ash: cleanup part 2.7
authorDenis Vlasenko <vda.linux@googlemail.com>
Fri, 23 Feb 2007 21:10:47 +0000 (21:10 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Fri, 23 Feb 2007 21:10:47 +0000 (21:10 -0000)
shell/ash.c

index 5c9060cad7165320a8eb7c4d6857ed7646afbef4..1de4cb50b554e13d512be6faad9136e3df8e271e 100644 (file)
@@ -1593,6 +1593,21 @@ struct shparam {
 
 static struct shparam shellparam;      /* $@ current positional parameters */
 
+/*
+ * Free the list of positional parameters.
+ */
+static void
+freeparam(volatile struct shparam *param)
+{
+       char **ap;
+
+       if (param->malloc) {
+               for (ap = param->p; *ap; ap++)
+                       free(*ap);
+               free(param->p);
+       }
+}
+
 #if ENABLE_ASH_GETOPTS
 static void
 getoptsreset(const char *value)
@@ -2871,29 +2886,6 @@ static const char syntax_index_table[258] = {
 #endif  /* USE_SIT_FUNCTION */
 
 
-/*      options.h */
-
-static void optschanged(void);
-static void setparam(char **);
-static void freeparam(volatile struct shparam *);
-static int shiftcmd(int, char **);
-static int setcmd(int, char **);
-static int nextopt(const char *);
-
-
-/*      redir.h      */
-
-/* flags passed to redirect */
-#define REDIR_PUSH 01           /* save previous values of file descriptors */
-#define REDIR_SAVEFD2 03       /* set preverrout */
-
-static void redirect(union node *, int);
-static void popredir(int);
-static void clearredir(int);
-static int copyfd(int, int);
-static int redirectsafe(union node *, int);
-
-
 /* ============ Alias handling */
 
 #if ENABLE_ASH_ALIAS
@@ -4622,393 +4614,744 @@ stoppedjobs(void)
 }
 
 
-/* ============ Routines to expand arguments to commands
+/* ============ redir.c
  *
- * We have to deal with backquotes, shell variables, and file metacharacters.
+ * Code for dealing with input/output redirection.
  */
 
-/*
- * expandarg flags
- */
-#define EXP_FULL        0x1     /* perform word splitting & file globbing */
-#define EXP_TILDE       0x2     /* do normal tilde expansion */
-#define EXP_VARTILDE    0x4     /* expand tildes in an assignment */
-#define EXP_REDIR       0x8     /* file glob for a redirection (1 match only) */
-#define EXP_CASE        0x10    /* keeps quotes around for CASE pattern */
-#define EXP_RECORD      0x20    /* need to record arguments for ifs breakup */
-#define EXP_VARTILDE2   0x40    /* expand tildes after colons only */
-#define EXP_WORD        0x80    /* expand word in parameter expansion */
-#define EXP_QWORD       0x100   /* expand word in quoted parameter expansion */
-/*
- * _rmescape() flags
- */
-#define RMESCAPE_ALLOC  0x1     /* Allocate a new string */
-#define RMESCAPE_GLOB   0x2     /* Add backslashes for glob */
-#define RMESCAPE_QUOTED 0x4     /* Remove CTLESC unless in quotes */
-#define RMESCAPE_GROW   0x8     /* Grow strings instead of stalloc */
-#define RMESCAPE_HEAP   0x10    /* Malloc strings instead of stalloc */
+#define EMPTY -2                /* marks an unused slot in redirtab */
+#ifndef PIPE_BUF
+# define PIPESIZE 4096          /* amount of buffering in a pipe */
+#else
+# define PIPESIZE PIPE_BUF
+#endif
 
 /*
- * Structure specifying which parts of the string should be searched
- * for IFS characters.
+ * Open a file in noclobber mode.
+ * The code was copied from bash.
  */
-struct ifsregion {
-       struct ifsregion *next; /* next region in list */
-       int begoff;             /* offset of start of region */
-       int endoff;             /* offset of end of region */
-       int nulonly;            /* search for nul bytes only */
-};
+static int
+noclobberopen(const char *fname)
+{
+       int r, fd;
+       struct stat finfo, finfo2;
 
-struct arglist {
-       struct strlist *list;
-       struct strlist **lastp;
-};
+       /*
+        * If the file exists and is a regular file, return an error
+        * immediately.
+        */
+       r = stat(fname, &finfo);
+       if (r == 0 && S_ISREG(finfo.st_mode)) {
+               errno = EEXIST;
+               return -1;
+       }
 
-/* output of current string */
-static char *expdest;
-/* list of back quote expressions */
-static struct nodelist *argbackq;
-/* first struct in list of ifs regions */
-static struct ifsregion ifsfirst;
-/* last struct in list */
-static struct ifsregion *ifslastp;
-/* holds expanded arg list */
-static struct arglist exparg;
+       /*
+        * If the file was not present (r != 0), make sure we open it
+        * exclusively so that if it is created before we open it, our open
+        * will fail.  Make sure that we do not truncate an existing file.
+        * Note that we don't turn on O_EXCL unless the stat failed -- if the
+        * file was not a regular file, we leave O_EXCL off.
+        */
+       if (r != 0)
+               return open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666);
+       fd = open(fname, O_WRONLY|O_CREAT, 0666);
+
+       /* If the open failed, return the file descriptor right away. */
+       if (fd < 0)
+               return fd;
+
+       /*
+        * OK, the open succeeded, but the file may have been changed from a
+        * non-regular file to a regular file between the stat and the open.
+        * We are assuming that the O_EXCL open handles the case where FILENAME
+        * did not exist and is symlinked to an existing file between the stat
+        * and open.
+        */
+
+       /*
+        * If we can open it and fstat the file descriptor, and neither check
+        * revealed that it was a regular file, and the file has not been
+        * replaced, return the file descriptor.
+        */
+       if (fstat(fd, &finfo2) == 0 && !S_ISREG(finfo2.st_mode)
+        && finfo.st_dev == finfo2.st_dev && finfo.st_ino == finfo2.st_ino)
+               return fd;
+
+       /* The file has been replaced.  badness. */
+       close(fd);
+       errno = EEXIST;
+       return -1;
+}
 
 /*
- * Our own itoa().
+ * Handle here documents.  Normally we fork off a process to write the
+ * data to a pipe.  If the document is short, we can stuff the data in
+ * the pipe without forking.
  */
+/* openhere needs this forward reference */
+static void expandhere(union node *arg, int fd);
 static int
-cvtnum(arith_t num)
+openhere(union node *redir)
 {
-       int len;
+       int pip[2];
+       size_t len = 0;
 
-       expdest = makestrspace(32, expdest);
-#if ENABLE_ASH_MATH_SUPPORT_64
-       len = fmtstr(expdest, 32, "%lld", (long long) num);
-#else
-       len = fmtstr(expdest, 32, "%ld", num);
+       if (pipe(pip) < 0)
+               ash_msg_and_raise_error("Pipe call failed");
+       if (redir->type == NHERE) {
+               len = strlen(redir->nhere.doc->narg.text);
+               if (len <= PIPESIZE) {
+                       full_write(pip[1], redir->nhere.doc->narg.text, len);
+                       goto out;
+               }
+       }
+       if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) {
+               close(pip[0]);
+               signal(SIGINT, SIG_IGN);
+               signal(SIGQUIT, SIG_IGN);
+               signal(SIGHUP, SIG_IGN);
+#ifdef SIGTSTP
+               signal(SIGTSTP, SIG_IGN);
 #endif
-       STADJUST(len, expdest);
-       return len;
+               signal(SIGPIPE, SIG_DFL);
+               if (redir->type == NHERE)
+                       full_write(pip[1], redir->nhere.doc->narg.text, len);
+               else
+                       expandhere(redir->nhere.doc, pip[1]);
+               _exit(0);
+       }
+ out:
+       close(pip[1]);
+       return pip[0];
 }
 
-static size_t
-esclen(const char *start, const char *p)
+static int
+openredirect(union node *redir)
 {
-       size_t esc = 0;
+       char *fname;
+       int f;
 
-       while (p > start && *--p == CTLESC) {
-               esc++;
+       switch (redir->nfile.type) {
+       case NFROM:
+               fname = redir->nfile.expfname;
+               f = open(fname, O_RDONLY);
+               if (f < 0)
+                       goto eopen;
+               break;
+       case NFROMTO:
+               fname = redir->nfile.expfname;
+               f = open(fname, O_RDWR|O_CREAT|O_TRUNC, 0666);
+               if (f < 0)
+                       goto ecreate;
+               break;
+       case NTO:
+               /* Take care of noclobber mode. */
+               if (Cflag) {
+                       fname = redir->nfile.expfname;
+                       f = noclobberopen(fname);
+                       if (f < 0)
+                               goto ecreate;
+                       break;
+               }
+               /* FALLTHROUGH */
+       case NCLOBBER:
+               fname = redir->nfile.expfname;
+               f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666);
+               if (f < 0)
+                       goto ecreate;
+               break;
+       case NAPPEND:
+               fname = redir->nfile.expfname;
+               f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666);
+               if (f < 0)
+                       goto ecreate;
+               break;
+       default:
+#if DEBUG
+               abort();
+#endif
+               /* Fall through to eliminate warning. */
+       case NTOFD:
+       case NFROMFD:
+               f = -1;
+               break;
+       case NHERE:
+       case NXHERE:
+               f = openhere(redir);
+               break;
        }
-       return esc;
+
+       return f;
+ ecreate:
+       ash_msg_and_raise_error("cannot create %s: %s", fname, errmsg(errno, "Directory nonexistent"));
+ eopen:
+       ash_msg_and_raise_error("cannot open %s: %s", fname, errmsg(errno, "No such file"));
 }
 
 /*
- * Remove any CTLESC characters from a string.
+ * Copy a file descriptor to be >= to.  Returns -1
+ * if the source file descriptor is closed, EMPTY if there are no unused
+ * file descriptors left.
  */
-static char *
-_rmescapes(char *str, int flag)
+static int
+copyfd(int from, int to)
 {
-       char *p, *q, *r;
-       static const char qchars[] = { CTLESC, CTLQUOTEMARK, 0 };
-       unsigned inquotes;
-       int notescaped;
-       int globbing;
+       int newfd;
 
-       p = strpbrk(str, qchars);
-       if (!p) {
-               return str;
-       }
-       q = p;
-       r = str;
-       if (flag & RMESCAPE_ALLOC) {
-               size_t len = p - str;
-               size_t fulllen = len + strlen(p) + 1;
-
-               if (flag & RMESCAPE_GROW) {
-                       r = makestrspace(fulllen, expdest);
-               } else if (flag & RMESCAPE_HEAP) {
-                       r = ckmalloc(fulllen);
-               } else {
-                       r = stalloc(fulllen);
-               }
-               q = r;
-               if (len > 0) {
-                       q = memcpy(q, str, len) + len;
-               }
+       newfd = fcntl(from, F_DUPFD, to);
+       if (newfd < 0) {
+               if (errno == EMFILE)
+                       return EMPTY;
+               ash_msg_and_raise_error("%d: %m", from);
        }
-       inquotes = (flag & RMESCAPE_QUOTED) ^ RMESCAPE_QUOTED;
-       globbing = flag & RMESCAPE_GLOB;
-       notescaped = globbing;
-       while (*p) {
-               if (*p == CTLQUOTEMARK) {
-                       inquotes = ~inquotes;
-                       p++;
-                       notescaped = globbing;
-                       continue;
-               }
-               if (*p == '\\') {
-                       /* naked back slash */
-                       notescaped = 0;
-                       goto copy;
-               }
-               if (*p == CTLESC) {
-                       p++;
-                       if (notescaped && inquotes && *p != '/') {
-                               *q++ = '\\';
-                       }
+       return newfd;
+}
+
+static void
+dupredirect(union node *redir, int f)
+{
+       int fd = redir->nfile.fd;
+
+       if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) {
+               if (redir->ndup.dupfd >= 0) {   /* if not ">&-" */
+                       copyfd(redir->ndup.dupfd, fd);
                }
-               notescaped = globbing;
- copy:
-               *q++ = *p++;
+               return;
        }
-       *q = '\0';
-       if (flag & RMESCAPE_GROW) {
-               expdest = r;
-               STADJUST(q - r + 1, expdest);
+
+       if (f != fd) {
+               copyfd(f, fd);
+               close(f);
        }
-       return r;
 }
-#define rmescapes(p) _rmescapes((p), 0)
-
-#define pmatch(a, b) !fnmatch((a), (b), 0)
 
 /*
- * Prepare a pattern for a expmeta (internal glob(3)) call.
- *
- * Returns an stalloced string.
+ * Process a list of redirection commands.  If the REDIR_PUSH flag is set,
+ * old file descriptors are stashed away so that the redirection can be
+ * undone by calling popredir.  If the REDIR_BACKQ flag is set, then the
+ * standard output, and the standard error if it becomes a duplicate of
+ * stdout, is saved in memory.
  */
-static char *
-preglob(const char *pattern, int quoted, int flag)
+/* flags passed to redirect */
+#define REDIR_PUSH    01        /* save previous values of file descriptors */
+#define REDIR_SAVEFD2 03        /* set preverrout */
+static void
+redirect(union node *redir, int flags)
 {
-       flag |= RMESCAPE_GLOB;
-       if (quoted) {
-               flag |= RMESCAPE_QUOTED;
+       union node *n;
+       struct redirtab *sv;
+       int i;
+       int fd;
+       int newfd;
+       int *p;
+       nullredirs++;
+       if (!redir) {
+               return;
        }
-       return _rmescapes((char *)pattern, flag);
+       sv = NULL;
+       INT_OFF;
+       if (flags & REDIR_PUSH) {
+               struct redirtab *q;
+               q = ckmalloc(sizeof(struct redirtab));
+               q->next = redirlist;
+               redirlist = q;
+               q->nullredirs = nullredirs - 1;
+               for (i = 0; i < 10; i++)
+                       q->renamed[i] = EMPTY;
+               nullredirs = 0;
+               sv = q;
+       }
+       n = redir;
+       do {
+               fd = n->nfile.fd;
+               if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD)
+                && n->ndup.dupfd == fd)
+                       continue; /* redirect from/to same file descriptor */
+
+               newfd = openredirect(n);
+               if (fd == newfd)
+                       continue;
+               if (sv && *(p = &sv->renamed[fd]) == EMPTY) {
+                       i = fcntl(fd, F_DUPFD, 10);
+
+                       if (i == -1) {
+                               i = errno;
+                               if (i != EBADF) {
+                                       close(newfd);
+                                       errno = i;
+                                       ash_msg_and_raise_error("%d: %m", fd);
+                                       /* NOTREACHED */
+                               }
+                       } else {
+                               *p = i;
+                               close(fd);
+                       }
+               } else {
+                       close(fd);
+               }
+               dupredirect(n, newfd);
+       } while ((n = n->nfile.next));
+       INT_ON;
+       if (flags & REDIR_SAVEFD2 && sv && sv->renamed[2] >= 0)
+               preverrout_fd = sv->renamed[2];
 }
 
 /*
- * Put a string on the stack.
+ * Undo the effects of the last redirection.
  */
 static void
-memtodest(const char *p, size_t len, int syntax, int quotes)
+popredir(int drop)
 {
-       char *q = expdest;
-
-       q = makestrspace(len * 2, q);
+       struct redirtab *rp;
+       int i;
 
-       while (len--) {
-               int c = signed_char2int(*p++);
-               if (!c)
-                       continue;
-               if (quotes && (SIT(c, syntax) == CCTL || SIT(c, syntax) == CBACK))
-                       USTPUTC(CTLESC, q);
-               USTPUTC(c, q);
+       if (--nullredirs >= 0)
+               return;
+       INT_OFF;
+       rp = redirlist;
+       for (i = 0; i < 10; i++) {
+               if (rp->renamed[i] != EMPTY) {
+                       if (!drop) {
+                               close(i);
+                               copyfd(rp->renamed[i], i);
+                       }
+                       close(rp->renamed[i]);
+               }
        }
-
-       expdest = q;
+       redirlist = rp->next;
+       nullredirs = rp->nullredirs;
+       free(rp);
+       INT_ON;
 }
 
-static void
-strtodest(const char *p, int syntax, int quotes)
-{
-       memtodest(p, strlen(p), syntax, quotes);
-}
+/*
+ * Undo all redirections.  Called on error or interrupt.
+ */
 
 /*
- * Record the fact that we have to scan this region of the
- * string for IFS characters.
+ * Discard all saved file descriptors.
  */
 static void
-recordregion(int start, int end, int nulonly)
+clearredir(int drop)
 {
-       struct ifsregion *ifsp;
-
-       if (ifslastp == NULL) {
-               ifsp = &ifsfirst;
-       } else {
-               INT_OFF;
-               ifsp = ckmalloc(sizeof(*ifsp));
-               ifsp->next = NULL;
-               ifslastp->next = ifsp;
-               INT_ON;
+       for (;;) {
+               nullredirs = 0;
+               if (!redirlist)
+                       break;
+               popredir(drop);
        }
-       ifslastp = ifsp;
-       ifslastp->begoff = start;
-       ifslastp->endoff = end;
-       ifslastp->nulonly = nulonly;
 }
 
-static void
-removerecordregions(int endoff)
+static int
+redirectsafe(union node *redir, int flags)
 {
-       if (ifslastp == NULL)
-               return;
-
-       if (ifsfirst.endoff > endoff) {
-               while (ifsfirst.next != NULL) {
-                       struct ifsregion *ifsp;
-                       INT_OFF;
-                       ifsp = ifsfirst.next->next;
-                       free(ifsfirst.next);
-                       ifsfirst.next = ifsp;
-                       INT_ON;
-               }
-               if (ifsfirst.begoff > endoff)
-                       ifslastp = NULL;
-               else {
-                       ifslastp = &ifsfirst;
-                       ifsfirst.endoff = endoff;
-               }
-               return;
-       }
+       int err;
+       volatile int saveint;
+       struct jmploc *volatile savehandler = exception_handler;
+       struct jmploc jmploc;
 
-       ifslastp = &ifsfirst;
-       while (ifslastp->next && ifslastp->next->begoff < endoff)
-               ifslastp=ifslastp->next;
-       while (ifslastp->next != NULL) {
-               struct ifsregion *ifsp;
-               INT_OFF;
-               ifsp = ifslastp->next->next;
-               free(ifslastp->next);
-               ifslastp->next = ifsp;
-               INT_ON;
+       SAVE_INT(saveint);
+       err = setjmp(jmploc.loc) * 2;
+       if (!err) {
+               exception_handler = &jmploc;
+               redirect(redir, flags);
        }
-       if (ifslastp->endoff > endoff)
-               ifslastp->endoff = endoff;
+       exception_handler = savehandler;
+       if (err && exception != EXERROR)
+               longjmp(exception_handler->loc, 1);
+       RESTORE_INT(saveint);
+       return err;
 }
 
-static char *
-exptilde(char *startp, char *p, int flag)
-{
-       char c;
-       char *name;
-       struct passwd *pw;
-       const char *home;
-       int quotes = flag & (EXP_FULL | EXP_CASE);
-       int startloc;
 
-       name = p + 1;
+/* ============ Routines to expand arguments to commands
+ *
+ * We have to deal with backquotes, shell variables, and file metacharacters.
+ */
 
-       while ((c = *++p) != '\0') {
-               switch (c) {
-               case CTLESC:
-                       return startp;
-               case CTLQUOTEMARK:
-                       return startp;
-               case ':':
-                       if (flag & EXP_VARTILDE)
-                               goto done;
-                       break;
-               case '/':
-               case CTLENDVAR:
-                       goto done;
-               }
-       }
- done:
-       *p = '\0';
-       if (*name == '\0') {
-               home = lookupvar(homestr);
-       } else {
-               pw = getpwnam(name);
-               if (pw == NULL)
-                       goto lose;
-               home = pw->pw_dir;
-       }
-       if (!home || !*home)
-               goto lose;
-       *p = c;
-       startloc = expdest - (char *)stackblock();
-       strtodest(home, SQSYNTAX, quotes);
-       recordregion(startloc, expdest - (char *)stackblock(), 0);
-       return p;
- lose:
-       *p = c;
-       return startp;
-}
+/*
+ * expandarg flags
+ */
+#define EXP_FULL        0x1     /* perform word splitting & file globbing */
+#define EXP_TILDE       0x2     /* do normal tilde expansion */
+#define EXP_VARTILDE    0x4     /* expand tildes in an assignment */
+#define EXP_REDIR       0x8     /* file glob for a redirection (1 match only) */
+#define EXP_CASE        0x10    /* keeps quotes around for CASE pattern */
+#define EXP_RECORD      0x20    /* need to record arguments for ifs breakup */
+#define EXP_VARTILDE2   0x40    /* expand tildes after colons only */
+#define EXP_WORD        0x80    /* expand word in parameter expansion */
+#define EXP_QWORD       0x100   /* expand word in quoted parameter expansion */
+/*
+ * _rmescape() flags
+ */
+#define RMESCAPE_ALLOC  0x1     /* Allocate a new string */
+#define RMESCAPE_GLOB   0x2     /* Add backslashes for glob */
+#define RMESCAPE_QUOTED 0x4     /* Remove CTLESC unless in quotes */
+#define RMESCAPE_GROW   0x8     /* Grow strings instead of stalloc */
+#define RMESCAPE_HEAP   0x10    /* Malloc strings instead of stalloc */
 
 /*
- * Execute a command inside back quotes.  If it's a builtin command, we
- * want to save its output in a block obtained from malloc.  Otherwise
- * we fork off a subprocess and get the output of the command via a pipe.
- * Should be called with interrupts off.
+ * Structure specifying which parts of the string should be searched
+ * for IFS characters.
  */
-struct backcmd {                /* result of evalbackcmd */
-       int fd;                 /* file descriptor to read from */
-       char *buf;              /* buffer */
-       int nleft;              /* number of chars in buffer */
-       struct job *jp;         /* job structure for command */
+struct ifsregion {
+       struct ifsregion *next; /* next region in list */
+       int begoff;             /* offset of start of region */
+       int endoff;             /* offset of end of region */
+       int nulonly;            /* search for nul bytes only */
 };
 
-/* These forward decls are needed to use "eval" code for backticks handling: */
-static int back_exitstatus; /* exit status of backquoted command */
-#define EV_EXIT 01              /* exit after evaluating tree */
-static void evaltree(union node *, int);
+struct arglist {
+       struct strlist *list;
+       struct strlist **lastp;
+};
 
-static void
-evalbackcmd(union node *n, struct backcmd *result)
+/* output of current string */
+static char *expdest;
+/* list of back quote expressions */
+static struct nodelist *argbackq;
+/* first struct in list of ifs regions */
+static struct ifsregion ifsfirst;
+/* last struct in list */
+static struct ifsregion *ifslastp;
+/* holds expanded arg list */
+static struct arglist exparg;
+
+/*
+ * Our own itoa().
+ */
+static int
+cvtnum(arith_t num)
 {
-       int saveherefd;
+       int len;
 
-       result->fd = -1;
-       result->buf = NULL;
-       result->nleft = 0;
-       result->jp = NULL;
-       if (n == NULL) {
-               goto out;
+       expdest = makestrspace(32, expdest);
+#if ENABLE_ASH_MATH_SUPPORT_64
+       len = fmtstr(expdest, 32, "%lld", (long long) num);
+#else
+       len = fmtstr(expdest, 32, "%ld", num);
+#endif
+       STADJUST(len, expdest);
+       return len;
+}
+
+static size_t
+esclen(const char *start, const char *p)
+{
+       size_t esc = 0;
+
+       while (p > start && *--p == CTLESC) {
+               esc++;
        }
+       return esc;
+}
 
-       saveherefd = herefd;
-       herefd = -1;
+/*
+ * Remove any CTLESC characters from a string.
+ */
+static char *
+_rmescapes(char *str, int flag)
+{
+       char *p, *q, *r;
+       static const char qchars[] = { CTLESC, CTLQUOTEMARK, 0 };
+       unsigned inquotes;
+       int notescaped;
+       int globbing;
 
-       {
-               int pip[2];
-               struct job *jp;
+       p = strpbrk(str, qchars);
+       if (!p) {
+               return str;
+       }
+       q = p;
+       r = str;
+       if (flag & RMESCAPE_ALLOC) {
+               size_t len = p - str;
+               size_t fulllen = len + strlen(p) + 1;
 
-               if (pipe(pip) < 0)
-                       ash_msg_and_raise_error("Pipe call failed");
-               jp = makejob(n, 1);
-               if (forkshell(jp, n, FORK_NOJOB) == 0) {
-                       FORCE_INT_ON;
-                       close(pip[0]);
-                       if (pip[1] != 1) {
-                               close(1);
-                               copyfd(pip[1], 1);
-                               close(pip[1]);
+               if (flag & RMESCAPE_GROW) {
+                       r = makestrspace(fulllen, expdest);
+               } else if (flag & RMESCAPE_HEAP) {
+                       r = ckmalloc(fulllen);
+               } else {
+                       r = stalloc(fulllen);
+               }
+               q = r;
+               if (len > 0) {
+                       q = memcpy(q, str, len) + len;
+               }
+       }
+       inquotes = (flag & RMESCAPE_QUOTED) ^ RMESCAPE_QUOTED;
+       globbing = flag & RMESCAPE_GLOB;
+       notescaped = globbing;
+       while (*p) {
+               if (*p == CTLQUOTEMARK) {
+                       inquotes = ~inquotes;
+                       p++;
+                       notescaped = globbing;
+                       continue;
+               }
+               if (*p == '\\') {
+                       /* naked back slash */
+                       notescaped = 0;
+                       goto copy;
+               }
+               if (*p == CTLESC) {
+                       p++;
+                       if (notescaped && inquotes && *p != '/') {
+                               *q++ = '\\';
                        }
-                       eflag = 0;
-                       evaltree(n, EV_EXIT); /* actually evaltreenr... */
-                       /* NOTREACHED */
                }
-               close(pip[1]);
-               result->fd = pip[0];
-               result->jp = jp;
+               notescaped = globbing;
+ copy:
+               *q++ = *p++;
        }
-       herefd = saveherefd;
- out:
-       TRACE(("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n",
-               result->fd, result->buf, result->nleft, result->jp));
+       *q = '\0';
+       if (flag & RMESCAPE_GROW) {
+               expdest = r;
+               STADJUST(q - r + 1, expdest);
+       }
+       return r;
+}
+#define rmescapes(p) _rmescapes((p), 0)
+
+#define pmatch(a, b) !fnmatch((a), (b), 0)
+
+/*
+ * Prepare a pattern for a expmeta (internal glob(3)) call.
+ *
+ * Returns an stalloced string.
+ */
+static char *
+preglob(const char *pattern, int quoted, int flag)
+{
+       flag |= RMESCAPE_GLOB;
+       if (quoted) {
+               flag |= RMESCAPE_QUOTED;
+       }
+       return _rmescapes((char *)pattern, flag);
 }
 
 /*
- * Expand stuff in backwards quotes.
+ * Put a string on the stack.
  */
 static void
-expbackq(union node *cmd, int quoted, int quotes)
+memtodest(const char *p, size_t len, int syntax, int quotes)
 {
-       struct backcmd in;
-       int i;
-       char buf[128];
-       char *p;
-       char *dest;
-       int startloc;
-       int syntax = quoted? DQSYNTAX : BASESYNTAX;
-       struct stackmark smark;
+       char *q = expdest;
 
-       INT_OFF;
-       setstackmark(&smark);
+       q = makestrspace(len * 2, q);
+
+       while (len--) {
+               int c = signed_char2int(*p++);
+               if (!c)
+                       continue;
+               if (quotes && (SIT(c, syntax) == CCTL || SIT(c, syntax) == CBACK))
+                       USTPUTC(CTLESC, q);
+               USTPUTC(c, q);
+       }
+
+       expdest = q;
+}
+
+static void
+strtodest(const char *p, int syntax, int quotes)
+{
+       memtodest(p, strlen(p), syntax, quotes);
+}
+
+/*
+ * Record the fact that we have to scan this region of the
+ * string for IFS characters.
+ */
+static void
+recordregion(int start, int end, int nulonly)
+{
+       struct ifsregion *ifsp;
+
+       if (ifslastp == NULL) {
+               ifsp = &ifsfirst;
+       } else {
+               INT_OFF;
+               ifsp = ckmalloc(sizeof(*ifsp));
+               ifsp->next = NULL;
+               ifslastp->next = ifsp;
+               INT_ON;
+       }
+       ifslastp = ifsp;
+       ifslastp->begoff = start;
+       ifslastp->endoff = end;
+       ifslastp->nulonly = nulonly;
+}
+
+static void
+removerecordregions(int endoff)
+{
+       if (ifslastp == NULL)
+               return;
+
+       if (ifsfirst.endoff > endoff) {
+               while (ifsfirst.next != NULL) {
+                       struct ifsregion *ifsp;
+                       INT_OFF;
+                       ifsp = ifsfirst.next->next;
+                       free(ifsfirst.next);
+                       ifsfirst.next = ifsp;
+                       INT_ON;
+               }
+               if (ifsfirst.begoff > endoff)
+                       ifslastp = NULL;
+               else {
+                       ifslastp = &ifsfirst;
+                       ifsfirst.endoff = endoff;
+               }
+               return;
+       }
+
+       ifslastp = &ifsfirst;
+       while (ifslastp->next && ifslastp->next->begoff < endoff)
+               ifslastp=ifslastp->next;
+       while (ifslastp->next != NULL) {
+               struct ifsregion *ifsp;
+               INT_OFF;
+               ifsp = ifslastp->next->next;
+               free(ifslastp->next);
+               ifslastp->next = ifsp;
+               INT_ON;
+       }
+       if (ifslastp->endoff > endoff)
+               ifslastp->endoff = endoff;
+}
+
+static char *
+exptilde(char *startp, char *p, int flag)
+{
+       char c;
+       char *name;
+       struct passwd *pw;
+       const char *home;
+       int quotes = flag & (EXP_FULL | EXP_CASE);
+       int startloc;
+
+       name = p + 1;
+
+       while ((c = *++p) != '\0') {
+               switch (c) {
+               case CTLESC:
+                       return startp;
+               case CTLQUOTEMARK:
+                       return startp;
+               case ':':
+                       if (flag & EXP_VARTILDE)
+                               goto done;
+                       break;
+               case '/':
+               case CTLENDVAR:
+                       goto done;
+               }
+       }
+ done:
+       *p = '\0';
+       if (*name == '\0') {
+               home = lookupvar(homestr);
+       } else {
+               pw = getpwnam(name);
+               if (pw == NULL)
+                       goto lose;
+               home = pw->pw_dir;
+       }
+       if (!home || !*home)
+               goto lose;
+       *p = c;
+       startloc = expdest - (char *)stackblock();
+       strtodest(home, SQSYNTAX, quotes);
+       recordregion(startloc, expdest - (char *)stackblock(), 0);
+       return p;
+ lose:
+       *p = c;
+       return startp;
+}
+
+/*
+ * Execute a command inside back quotes.  If it's a builtin command, we
+ * want to save its output in a block obtained from malloc.  Otherwise
+ * we fork off a subprocess and get the output of the command via a pipe.
+ * Should be called with interrupts off.
+ */
+struct backcmd {                /* result of evalbackcmd */
+       int fd;                 /* file descriptor to read from */
+       char *buf;              /* buffer */
+       int nleft;              /* number of chars in buffer */
+       struct job *jp;         /* job structure for command */
+};
+
+/* These forward decls are needed to use "eval" code for backticks handling: */
+static int back_exitstatus; /* exit status of backquoted command */
+#define EV_EXIT 01              /* exit after evaluating tree */
+static void evaltree(union node *, int);
+
+static void
+evalbackcmd(union node *n, struct backcmd *result)
+{
+       int saveherefd;
+
+       result->fd = -1;
+       result->buf = NULL;
+       result->nleft = 0;
+       result->jp = NULL;
+       if (n == NULL) {
+               goto out;
+       }
+
+       saveherefd = herefd;
+       herefd = -1;
+
+       {
+               int pip[2];
+               struct job *jp;
+
+               if (pipe(pip) < 0)
+                       ash_msg_and_raise_error("Pipe call failed");
+               jp = makejob(n, 1);
+               if (forkshell(jp, n, FORK_NOJOB) == 0) {
+                       FORCE_INT_ON;
+                       close(pip[0]);
+                       if (pip[1] != 1) {
+                               close(1);
+                               copyfd(pip[1], 1);
+                               close(pip[1]);
+                       }
+                       eflag = 0;
+                       evaltree(n, EV_EXIT); /* actually evaltreenr... */
+                       /* NOTREACHED */
+               }
+               close(pip[1]);
+               result->fd = pip[0];
+               result->jp = jp;
+       }
+       herefd = saveherefd;
+ out:
+       TRACE(("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n",
+               result->fd, result->buf, result->nleft, result->jp));
+}
+
+/*
+ * Expand stuff in backwards quotes.
+ */
+static void
+expbackq(union node *cmd, int quoted, int quotes)
+{
+       struct backcmd in;
+       int i;
+       char buf[128];
+       char *p;
+       char *dest;
+       int startloc;
+       int syntax = quoted? DQSYNTAX : BASESYNTAX;
+       struct stackmark smark;
+
+       INT_OFF;
+       setstackmark(&smark);
        dest = expdest;
        startloc = dest - (char *)stackblock();
        grabstackstr(dest);
@@ -7475,43 +7818,95 @@ evalpipe(union node *n, int flags)
        INT_ON;
 }
 
-static struct localvar *localvars;
-
 /*
- * Called after a function returns.
- * Interrupts must be off.
+ * Controls whether the shell is interactive or not.
  */
 static void
-poplocalvars(void)
+setinteractive(int on)
 {
-       struct localvar *lvp;
-       struct var *vp;
+       static int is_interactive;
 
-       while ((lvp = localvars) != NULL) {
-               localvars = lvp->next;
-               vp = lvp->vp;
-               TRACE(("poplocalvar %s", vp ? vp->text : "-"));
-               if (vp == NULL) {       /* $- saved */
-                       memcpy(optlist, lvp->text, sizeof(optlist));
-                       free((char*)lvp->text);
-                       optschanged();
-               } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) {
-                       unsetvar(vp->text);
-               } else {
-                       if (vp->func)
-                               (*vp->func)(strchrnul(lvp->text, '=') + 1);
-                       if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
-                               free((char*)vp->text);
-                       vp->flags = lvp->flags;
-                       vp->text = lvp->text;
-               }
-               free(lvp);
-       }
-}
+       if (++on == is_interactive)
+               return;
+       is_interactive = on;
+       setsignal(SIGINT);
+       setsignal(SIGQUIT);
+       setsignal(SIGTERM);
+#if !ENABLE_FEATURE_SH_EXTRA_QUIET
+       if (is_interactive > 1) {
+               /* Looks like they want an interactive shell */
+               static smallint do_banner;
 
-static int
-evalfun(struct funcnode *func, int argc, char **argv, int flags)
-{
+               if (!do_banner) {
+                       out1fmt(
+                               "\n\n"
+                               "%s Built-in shell (ash)\n"
+                               "Enter 'help' for a list of built-in commands."
+                               "\n\n",
+                               BB_BANNER);
+                       do_banner = 1;
+               }
+       }
+#endif
+}
+
+#if ENABLE_FEATURE_EDITING_VI
+#define setvimode(on) do { \
+       if (on) line_input_state->flags |= VI_MODE; \
+       else line_input_state->flags &= ~VI_MODE; \
+} while (0)
+#else
+#define setvimode(on) viflag = 0   /* forcibly keep the option off */
+#endif
+
+static void
+optschanged(void)
+{
+#if DEBUG
+       opentrace();
+#endif
+       setinteractive(iflag);
+       setjobctl(mflag);
+       setvimode(viflag);
+}
+
+static struct localvar *localvars;
+
+/*
+ * Called after a function returns.
+ * Interrupts must be off.
+ */
+static void
+poplocalvars(void)
+{
+       struct localvar *lvp;
+       struct var *vp;
+
+       while ((lvp = localvars) != NULL) {
+               localvars = lvp->next;
+               vp = lvp->vp;
+               TRACE(("poplocalvar %s", vp ? vp->text : "-"));
+               if (vp == NULL) {       /* $- saved */
+                       memcpy(optlist, lvp->text, sizeof(optlist));
+                       free((char*)lvp->text);
+                       optschanged();
+               } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) {
+                       unsetvar(vp->text);
+               } else {
+                       if (vp->func)
+                               (*vp->func)(strchrnul(lvp->text, '=') + 1);
+                       if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
+                               free((char*)vp->text);
+                       vp->flags = lvp->flags;
+                       vp->text = lvp->text;
+               }
+               free(lvp);
+       }
+}
+
+static int
+evalfun(struct funcnode *func, int argc, char **argv, int flags)
+{
        volatile struct shparam saveparam;
        struct localvar *volatile savelocalvars;
        struct jmploc *volatile savehandler;
@@ -7894,7 +8289,7 @@ evalcommand(union node *cmd, int flags)
 
        preverrout_fd = 2;
        expredir(cmd->ncmd.redirect);
-       status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH|REDIR_SAVEFD2);
+       status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH | REDIR_SAVEFD2);
 
        path = vpath.text;
        for (argp = cmd->ncmd.assign; argp; argp = argp->narg.next) {
@@ -8678,88 +9073,35 @@ changemail(const char *val)
 /* ============ ??? */
 
 /*
- * Take commands from a file.  To be compatible we should do a path
- * search for the file, which is necessary to find sub-commands.
- */
-static char *
-find_dot_file(char *name)
-{
-       char *fullname;
-       const char *path = pathval();
-       struct stat statb;
-
-       /* don't try this for absolute or relative paths */
-       if (strchr(name, '/'))
-               return name;
-
-       while ((fullname = padvance(&path, name)) != NULL) {
-               if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) {
-                       /*
-                        * Don't bother freeing here, since it will
-                        * be freed by the caller.
-                        */
-                       return fullname;
-               }
-               stunalloc(fullname);
-       }
-
-       /* not found in the PATH */
-       ash_msg_and_raise_error("%s: not found", name);
-       /* NOTREACHED */
-}
-
-/*
- * Controls whether the shell is interactive or not.
+ * Set the shell parameters.
  */
 static void
-setinteractive(int on)
+setparam(char **argv)
 {
-       static int is_interactive;
-
-       if (++on == is_interactive)
-               return;
-       is_interactive = on;
-       setsignal(SIGINT);
-       setsignal(SIGQUIT);
-       setsignal(SIGTERM);
-#if !ENABLE_FEATURE_SH_EXTRA_QUIET
-       if (is_interactive > 1) {
-               /* Looks like they want an interactive shell */
-               static smallint do_banner;
+       char **newparam;
+       char **ap;
+       int nparam;
 
-               if (!do_banner) {
-                       out1fmt(
-                               "\n\n"
-                               "%s Built-in shell (ash)\n"
-                               "Enter 'help' for a list of built-in commands."
-                               "\n\n",
-                               BB_BANNER);
-                       do_banner = 1;
-               }
+       for (nparam = 0; argv[nparam]; nparam++);
+       ap = newparam = ckmalloc((nparam + 1) * sizeof(*ap));
+       while (*argv) {
+               *ap++ = ckstrdup(*argv++);
        }
+       *ap = NULL;
+       freeparam(&shellparam);
+       shellparam.malloc = 1;
+       shellparam.nparam = nparam;
+       shellparam.p = newparam;
+#if ENABLE_ASH_GETOPTS
+       shellparam.optind = 1;
+       shellparam.optoff = -1;
 #endif
 }
 
-#if ENABLE_FEATURE_EDITING_VI
-#define setvimode(on) do { \
-       if (on) line_input_state->flags |= VI_MODE; \
-       else line_input_state->flags &= ~VI_MODE; \
-} while (0)
-#else
-#define setvimode(on) viflag = 0   /* forcibly keep the option off */
-#endif
-
-static void
-optschanged(void)
-{
-#if DEBUG
-       opentrace();
-#endif
-       setinteractive(iflag);
-       setjobctl(mflag);
-       setvimode(viflag);
-}
-
+/*
+ * Process shell options.  The global variable argptr contains a pointer
+ * to the argument list; we advance it past the options.
+ */
 static void
 minus_o(char *name, int val)
 {
@@ -8779,7 +9121,6 @@ minus_o(char *name, int val)
                out1fmt("%-16s%s\n", optnames(i),
                                optlist[i] ? "on" : "off");
 }
-
 static void
 setoption(int flag, int val)
 {
@@ -8794,11 +9135,6 @@ setoption(int flag, int val)
        ash_msg_and_raise_error("Illegal option -%c", flag);
        /* NOTREACHED */
 }
-
-/*
- * Process shell options.  The global variable argptr contains a pointer
- * to the argument list; we advance it past the options.
- */
 static void
 options(int cmdline)
 {
@@ -8848,47 +9184,6 @@ options(int cmdline)
        }
 }
 
-/*
- * Set the shell parameters.
- */
-static void
-setparam(char **argv)
-{
-       char **newparam;
-       char **ap;
-       int nparam;
-
-       for (nparam = 0; argv[nparam]; nparam++);
-       ap = newparam = ckmalloc((nparam + 1) * sizeof(*ap));
-       while (*argv) {
-               *ap++ = ckstrdup(*argv++);
-       }
-       *ap = NULL;
-       freeparam(&shellparam);
-       shellparam.malloc = 1;
-       shellparam.nparam = nparam;
-       shellparam.p = newparam;
-#if ENABLE_ASH_GETOPTS
-       shellparam.optind = 1;
-       shellparam.optoff = -1;
-#endif
-}
-
-/*
- * Free the list of positional parameters.
- */
-static void
-freeparam(volatile struct shparam *param)
-{
-       char **ap;
-
-       if (param->malloc) {
-               for (ap = param->p; *ap; ap++)
-                       free(*ap);
-               free(param->p);
-       }
-}
-
 /*
  * The shift builtin command.
  */
@@ -10720,6 +11015,37 @@ cmdloop(int top)
        return 0;
 }
 
+/*
+ * Take commands from a file.  To be compatible we should do a path
+ * search for the file, which is necessary to find sub-commands.
+ */
+static char *
+find_dot_file(char *name)
+{
+       char *fullname;
+       const char *path = pathval();
+       struct stat statb;
+
+       /* don't try this for absolute or relative paths */
+       if (strchr(name, '/'))
+               return name;
+
+       while ((fullname = padvance(&path, name)) != NULL) {
+               if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) {
+                       /*
+                        * Don't bother freeing here, since it will
+                        * be freed by the caller.
+                        */
+                       return fullname;
+               }
+               stunalloc(fullname);
+       }
+
+       /* not found in the PATH */
+       ash_msg_and_raise_error("%s: not found", name);
+       /* NOTREACHED */
+}
+
 static int
 dotcmd(int argc, char **argv)
 {
@@ -10982,352 +11308,6 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path)
 }
 
 
-/* ============ redir.c
- *
- * Code for dealing with input/output redirection.
- */
-
-#define EMPTY -2                /* marks an unused slot in redirtab */
-#ifndef PIPE_BUF
-# define PIPESIZE 4096          /* amount of buffering in a pipe */
-#else
-# define PIPESIZE PIPE_BUF
-#endif
-
-/*
- * Open a file in noclobber mode.
- * The code was copied from bash.
- */
-static int
-noclobberopen(const char *fname)
-{
-       int r, fd;
-       struct stat finfo, finfo2;
-
-       /*
-        * If the file exists and is a regular file, return an error
-        * immediately.
-        */
-       r = stat(fname, &finfo);
-       if (r == 0 && S_ISREG(finfo.st_mode)) {
-               errno = EEXIST;
-               return -1;
-       }
-
-       /*
-        * If the file was not present (r != 0), make sure we open it
-        * exclusively so that if it is created before we open it, our open
-        * will fail.  Make sure that we do not truncate an existing file.
-        * Note that we don't turn on O_EXCL unless the stat failed -- if the
-        * file was not a regular file, we leave O_EXCL off.
-        */
-       if (r != 0)
-               return open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666);
-       fd = open(fname, O_WRONLY|O_CREAT, 0666);
-
-       /* If the open failed, return the file descriptor right away. */
-       if (fd < 0)
-               return fd;
-
-       /*
-        * OK, the open succeeded, but the file may have been changed from a
-        * non-regular file to a regular file between the stat and the open.
-        * We are assuming that the O_EXCL open handles the case where FILENAME
-        * did not exist and is symlinked to an existing file between the stat
-        * and open.
-        */
-
-       /*
-        * If we can open it and fstat the file descriptor, and neither check
-        * revealed that it was a regular file, and the file has not been
-        * replaced, return the file descriptor.
-        */
-       if (fstat(fd, &finfo2) == 0 && !S_ISREG(finfo2.st_mode)
-        && finfo.st_dev == finfo2.st_dev && finfo.st_ino == finfo2.st_ino)
-               return fd;
-
-       /* The file has been replaced.  badness. */
-       close(fd);
-       errno = EEXIST;
-       return -1;
-}
-
-/*
- * Handle here documents.  Normally we fork off a process to write the
- * data to a pipe.  If the document is short, we can stuff the data in
- * the pipe without forking.
- */
-static int
-openhere(union node *redir)
-{
-       int pip[2];
-       size_t len = 0;
-
-       if (pipe(pip) < 0)
-               ash_msg_and_raise_error("Pipe call failed");
-       if (redir->type == NHERE) {
-               len = strlen(redir->nhere.doc->narg.text);
-               if (len <= PIPESIZE) {
-                       full_write(pip[1], redir->nhere.doc->narg.text, len);
-                       goto out;
-               }
-       }
-       if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) {
-               close(pip[0]);
-               signal(SIGINT, SIG_IGN);
-               signal(SIGQUIT, SIG_IGN);
-               signal(SIGHUP, SIG_IGN);
-#ifdef SIGTSTP
-               signal(SIGTSTP, SIG_IGN);
-#endif
-               signal(SIGPIPE, SIG_DFL);
-               if (redir->type == NHERE)
-                       full_write(pip[1], redir->nhere.doc->narg.text, len);
-               else
-                       expandhere(redir->nhere.doc, pip[1]);
-               _exit(0);
-       }
- out:
-       close(pip[1]);
-       return pip[0];
-}
-
-static int
-openredirect(union node *redir)
-{
-       char *fname;
-       int f;
-
-       switch (redir->nfile.type) {
-       case NFROM:
-               fname = redir->nfile.expfname;
-               f = open(fname, O_RDONLY);
-               if (f < 0)
-                       goto eopen;
-               break;
-       case NFROMTO:
-               fname = redir->nfile.expfname;
-               f = open(fname, O_RDWR|O_CREAT|O_TRUNC, 0666);
-               if (f < 0)
-                       goto ecreate;
-               break;
-       case NTO:
-               /* Take care of noclobber mode. */
-               if (Cflag) {
-                       fname = redir->nfile.expfname;
-                       f = noclobberopen(fname);
-                       if (f < 0)
-                               goto ecreate;
-                       break;
-               }
-               /* FALLTHROUGH */
-       case NCLOBBER:
-               fname = redir->nfile.expfname;
-               f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666);
-               if (f < 0)
-                       goto ecreate;
-               break;
-       case NAPPEND:
-               fname = redir->nfile.expfname;
-               f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666);
-               if (f < 0)
-                       goto ecreate;
-               break;
-       default:
-#if DEBUG
-               abort();
-#endif
-               /* Fall through to eliminate warning. */
-       case NTOFD:
-       case NFROMFD:
-               f = -1;
-               break;
-       case NHERE:
-       case NXHERE:
-               f = openhere(redir);
-               break;
-       }
-
-       return f;
- ecreate:
-       ash_msg_and_raise_error("cannot create %s: %s", fname, errmsg(errno, "Directory nonexistent"));
- eopen:
-       ash_msg_and_raise_error("cannot open %s: %s", fname, errmsg(errno, "No such file"));
-}
-
-static void
-dupredirect(union node *redir, int f)
-{
-       int fd = redir->nfile.fd;
-
-       if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) {
-               if (redir->ndup.dupfd >= 0) {   /* if not ">&-" */
-                       copyfd(redir->ndup.dupfd, fd);
-               }
-               return;
-       }
-
-       if (f != fd) {
-               copyfd(f, fd);
-               close(f);
-       }
-}
-
-/*
- * Process a list of redirection commands.  If the REDIR_PUSH flag is set,
- * old file descriptors are stashed away so that the redirection can be
- * undone by calling popredir.  If the REDIR_BACKQ flag is set, then the
- * standard output, and the standard error if it becomes a duplicate of
- * stdout, is saved in memory.
- */
-static void
-redirect(union node *redir, int flags)
-{
-       union node *n;
-       struct redirtab *sv;
-       int i;
-       int fd;
-       int newfd;
-       int *p;
-       nullredirs++;
-       if (!redir) {
-               return;
-       }
-       sv = NULL;
-       INT_OFF;
-       if (flags & REDIR_PUSH) {
-               struct redirtab *q;
-               q = ckmalloc(sizeof(struct redirtab));
-               q->next = redirlist;
-               redirlist = q;
-               q->nullredirs = nullredirs - 1;
-               for (i = 0; i < 10; i++)
-                       q->renamed[i] = EMPTY;
-               nullredirs = 0;
-               sv = q;
-       }
-       n = redir;
-       do {
-               fd = n->nfile.fd;
-               if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD)
-                && n->ndup.dupfd == fd)
-                       continue; /* redirect from/to same file descriptor */
-
-               newfd = openredirect(n);
-               if (fd == newfd)
-                       continue;
-               if (sv && *(p = &sv->renamed[fd]) == EMPTY) {
-                       i = fcntl(fd, F_DUPFD, 10);
-
-                       if (i == -1) {
-                               i = errno;
-                               if (i != EBADF) {
-                                       close(newfd);
-                                       errno = i;
-                                       ash_msg_and_raise_error("%d: %m", fd);
-                                       /* NOTREACHED */
-                               }
-                       } else {
-                               *p = i;
-                               close(fd);
-                       }
-               } else {
-                       close(fd);
-               }
-               dupredirect(n, newfd);
-       } while ((n = n->nfile.next));
-       INT_ON;
-       if (flags & REDIR_SAVEFD2 && sv && sv->renamed[2] >= 0)
-               preverrout_fd = sv->renamed[2];
-}
-
-/*
- * Undo the effects of the last redirection.
- */
-static void
-popredir(int drop)
-{
-       struct redirtab *rp;
-       int i;
-
-       if (--nullredirs >= 0)
-               return;
-       INT_OFF;
-       rp = redirlist;
-       for (i = 0; i < 10; i++) {
-               if (rp->renamed[i] != EMPTY) {
-                       if (!drop) {
-                               close(i);
-                               copyfd(rp->renamed[i], i);
-                       }
-                       close(rp->renamed[i]);
-               }
-       }
-       redirlist = rp->next;
-       nullredirs = rp->nullredirs;
-       free(rp);
-       INT_ON;
-}
-
-/*
- * Undo all redirections.  Called on error or interrupt.
- */
-
-/*
- * Discard all saved file descriptors.
- */
-static void
-clearredir(int drop)
-{
-       for (;;) {
-               nullredirs = 0;
-               if (!redirlist)
-                       break;
-               popredir(drop);
-       }
-}
-
-/*
- * Copy a file descriptor to be >= to.  Returns -1
- * if the source file descriptor is closed, EMPTY if there are no unused
- * file descriptors left.
- */
-static int
-copyfd(int from, int to)
-{
-       int newfd;
-
-       newfd = fcntl(from, F_DUPFD, to);
-       if (newfd < 0) {
-               if (errno == EMFILE)
-                       return EMPTY;
-               ash_msg_and_raise_error("%d: %m", from);
-       }
-       return newfd;
-}
-
-static int
-redirectsafe(union node *redir, int flags)
-{
-       int err;
-       volatile int saveint;
-       struct jmploc *volatile savehandler = exception_handler;
-       struct jmploc jmploc;
-
-       SAVE_INT(saveint);
-       err = setjmp(jmploc.loc) * 2;
-       if (!err) {
-               exception_handler = &jmploc;
-               redirect(redir, flags);
-       }
-       exception_handler = savehandler;
-       if (err && exception != EXERROR)
-               longjmp(exception_handler->loc, 1);
-       RESTORE_INT(saveint);
-       return err;
-}
-
-
 /* ============ trap.c */
 
 /*