BSD 4_3_Net_2 release
[unix-history] / usr / src / lib / libc / gen / fts.c
index 2a3ede0..e2958d3 100644 (file)
@@ -2,60 +2,68 @@
  * Copyright (c) 1990 The Regents of the University of California.
  * All rights reserved.
  *
  * Copyright (c) 1990 The Regents of the University of California.
  * All rights reserved.
  *
- * Redistribution and use in source and binary forms are permitted provided
- * that: (1) source distributions retain this entire copyright notice and
- * comment, and (2) distributions including binaries display the following
- * acknowledgement:  ``This product includes software developed by the
- * University of California, Berkeley and its contributors'' in the
- * documentation or other materials provided with the distribution and in
- * all advertising materials mentioning features or use of this software.
- * Neither the name of the University nor the names of its contributors may
- * be used to endorse or promote products derived from this software without
- * specific prior written permission.
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
  */
 
 #if defined(LIBC_SCCS) && !defined(lint)
  */
 
 #if defined(LIBC_SCCS) && !defined(lint)
-static char sccsid[] = "@(#)fts.c      5.10 (Berkeley) 6/9/90";
+static char sccsid[] = "@(#)fts.c      5.19 (Berkeley) 5/9/91";
 #endif /* LIBC_SCCS and not lint */
 
 #endif /* LIBC_SCCS and not lint */
 
+#include <sys/cdefs.h>
 #include <sys/param.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <dirent.h>
 #include <errno.h>
 #include <sys/param.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <dirent.h>
 #include <errno.h>
-#include <fts.h>
-#include <string.h>
+#include "fts.h"
 #include <stdlib.h>
 #include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
 
 
-FTSENT *fts_alloc(), *fts_build(), *fts_cycle(), *fts_sort(), *fts_root();
-short fts_stat();
-
-/*
- * Special case a root of "/" so that slashes aren't appended causing
- * paths to be written as "//foo".
- */
-#define        NAPPEND(p) \
-       (p->fts_level == ROOTLEVEL && p->fts_pathlen == 1 && \
-           p->fts_path[0] == '/' ? 0 : p->fts_pathlen)
+static FTSENT *fts_alloc(), *fts_build(), *fts_sort();
+static void fts_load(), fts_lfree();
+static u_short fts_stat();
+static char *fts_path();
 
 
-#define        CHDIR(sp, path) (!(sp->fts_options & FTS_NOCHDIR) && chdir(path))
-#define        FCHDIR(sp, fd)  (!(sp->fts_options & FTS_NOCHDIR) && fchdir(fd))
+#define        ISSET(opt)      (sp->fts_options & opt)
+#define        SET(opt)        (sp->fts_options |= opt)
 
 
-#define        ROOTLEVEL       0
-#define        ROOTPARENTLEVEL -1
+#define        CHDIR(sp, path) (!ISSET(FTS_NOCHDIR) && chdir(path))
+#define        FCHDIR(sp, fd)  (!ISSET(FTS_NOCHDIR) && fchdir(fd))
 
 /* fts_build flags */
 
 /* fts_build flags */
-#define        BCHILD          1               /* from ftschildren */
-#define        BREAD           2               /* from ftsread */
-
-static FTS *stream;                    /* current stream pointer */
+#define        BCHILD          1               /* from fts_children */
+#define        BREAD           2               /* from fts_read */
 
 FTS *
 
 FTS *
-ftsopen(argv, options, compar)
-       char *argv[];
+fts_open(argv, options, compar)
+       char * const *argv;
        register int options;
        int (*compar)();
 {
        register int options;
        int (*compar)();
 {
@@ -63,36 +71,38 @@ ftsopen(argv, options, compar)
        register FTSENT *p, *root;
        register int nitems, maxlen;
        FTSENT *parent, *tmp;
        register FTSENT *p, *root;
        register int nitems, maxlen;
        FTSENT *parent, *tmp;
-       char *fts_path();
+       int len;
 
 
-       /* allocate/initialize the stream */
-       if (!(stream = sp = (FTS *)malloc((u_int)sizeof(FTS))))
+       /* Allocate/initialize the stream */
+       if (!(sp = (FTS *)malloc((u_int)sizeof(FTS))))
                return(NULL);
        bzero(sp, sizeof(FTS));
        sp->fts_compar = compar;
                return(NULL);
        bzero(sp, sizeof(FTS));
        sp->fts_compar = compar;
-
-       /*
-        * logical walks turn on NOCHDIR; symbolic links are just too
-        * hard to deal with.
-        */
        sp->fts_options = options;
        sp->fts_options = options;
-       if (options & FTS_LOGICAL)
-               sp->fts_options |= FTS_NOCHDIR;
 
 
-       /* allocate/initialize root's parent */
-       if (!(parent = fts_alloc("", 0)))
+       /* Logical walks turn on NOCHDIR; symbolic links are too hard. */
+       if (ISSET(FTS_LOGICAL))
+               SET(FTS_NOCHDIR);
+
+       /* Allocate/initialize root's parent. */
+       if (!(parent = fts_alloc(sp, "", 0)))
                goto mem1;
                goto mem1;
-       parent->fts_level = ROOTPARENTLEVEL;
+       parent->fts_level = FTS_ROOTPARENTLEVEL;
 
 
-       /* allocate/initialize root(s) */
+       /* Allocate/initialize root(s). */
        maxlen = -1;
        for (root = NULL, nitems = 0; *argv; ++argv, ++nitems) {
        maxlen = -1;
        for (root = NULL, nitems = 0; *argv; ++argv, ++nitems) {
-               if (!(p = fts_root(*argv)))
+               if (!(len = strlen(*argv))) {
+                       errno = ENOENT;
                        goto mem2;
                        goto mem2;
-               if (maxlen < p->fts_namelen)
-                       maxlen = p->fts_namelen;
+               }
+               if (maxlen < len)
+                       maxlen = len;
+               p = fts_alloc(sp, *argv, len);
+               p->fts_level = FTS_ROOTLEVEL;
+               p->fts_parent = parent;
                /*
                /*
-                * if comparison routine supplied, traverse in sorted
+                * If comparison routine supplied, traverse in sorted
                 * order; otherwise traverse in the order specified.
                 */
                if (compar) {
                 * order; otherwise traverse in the order specified.
                 */
                if (compar) {
@@ -100,7 +110,7 @@ ftsopen(argv, options, compar)
                        root = p;
                        p->fts_accpath = p->fts_name;
                        if (!(options & FTS_NOSTAT))
                        root = p;
                        p->fts_accpath = p->fts_name;
                        if (!(options & FTS_NOSTAT))
-                               p->fts_info = fts_stat(p, 0);
+                               p->fts_info = fts_stat(sp, p, 0);
                } else {
                        p->fts_link = NULL;
                        if (!root)
                } else {
                        p->fts_link = NULL;
                        if (!root)
@@ -110,69 +120,71 @@ ftsopen(argv, options, compar)
                                tmp = p;
                        }
                }
                                tmp = p;
                        }
                }
-               p->fts_level = ROOTLEVEL;
-               p->fts_parent = parent;
        }
        if (compar && nitems > 1)
        }
        if (compar && nitems > 1)
-               root = fts_sort(root, nitems);
+               root = fts_sort(sp, root, nitems);
 
        /*
 
        /*
-        * allocate a dummy pointer and make ftsread think that we've just
+        * Allocate a dummy pointer and make fts_read think that we've just
         * finished the node before the root(s); set p->fts_info to FTS_NS
         * so that everything about the "current" node is ignored.
         */
         * finished the node before the root(s); set p->fts_info to FTS_NS
         * so that everything about the "current" node is ignored.
         */
-       if (!(sp->fts_cur = fts_alloc("", 0)))
+       if (!(sp->fts_cur = fts_alloc(sp, "", 0)))
                goto mem2;
        sp->fts_cur->fts_link = root;
        sp->fts_cur->fts_info = FTS_NS;
 
                goto mem2;
        sp->fts_cur->fts_link = root;
        sp->fts_cur->fts_info = FTS_NS;
 
-       /* start out with at least 1K+ of path space */
-       if (!fts_path(MAX(maxlen, MAXPATHLEN)))
+       /* Start out with at least 1K+ of path space. */
+       if (!fts_path(sp, MAX(maxlen, MAXPATHLEN)))
                goto mem3;
 
        /*
                goto mem3;
 
        /*
-        * if using chdir(2), grab a file descriptor pointing to dot to insure
+        * If using chdir(2), grab a file descriptor pointing to dot to insure
         * that we can get back here; this could be avoided for some paths,
         * but almost certainly not worth the effort.  Slashes, symbolic links,
         * and ".." are all fairly nasty problems.  Note, if we can't get the
         * descriptor we run anyway, just more slowly.
         */
         * that we can get back here; this could be avoided for some paths,
         * but almost certainly not worth the effort.  Slashes, symbolic links,
         * and ".." are all fairly nasty problems.  Note, if we can't get the
         * descriptor we run anyway, just more slowly.
         */
-       if (!(options & FTS_NOCHDIR) &&
-           (sp->fts_sd = open(".", O_RDONLY, 0)) < 0)
-               sp->fts_options |= FTS_NOCHDIR;
+       if (!ISSET(FTS_NOCHDIR) && (sp->fts_rfd = open(".", O_RDONLY, 0)) < 0)
+               SET(FTS_NOCHDIR);
 
        return(sp);
 
 
        return(sp);
 
-mem3:  (void)free((void *)sp->fts_cur);
+mem3:  free(sp->fts_cur);
 mem2:  fts_lfree(root);
 mem2:  fts_lfree(root);
-       (void)free((void *)parent);
-mem1:  (void)free((void *)sp);
+       free(parent);
+mem1:  free(sp);
        return(NULL);
 }
 
        return(NULL);
 }
 
-static
-fts_load(p)
+static void
+fts_load(sp, p)
+       FTS *sp;
        register FTSENT *p;
 {
        register int len;
        register char *cp;
 
        /*
        register FTSENT *p;
 {
        register int len;
        register char *cp;
 
        /*
-        * load the stream structure for the next traversal; set the
-        * accpath field specially so the chdir gets done to the right
+        * Load the stream structure for the next traversal.  Since we don't
+        * actually enter the directory until after the preorder visit, set
+        * the fts_accpath field specially so the chdir gets done to the right
         * place and the user can access the first node.
         */
        len = p->fts_pathlen = p->fts_namelen;
         * place and the user can access the first node.
         */
        len = p->fts_pathlen = p->fts_namelen;
-       bcopy(p->fts_name, stream->fts_path, len + 1);
+       bcopy(p->fts_name, sp->fts_path, len + 1);
        if ((cp = rindex(p->fts_name, '/')) && (cp != p->fts_name || cp[1])) {
                len = strlen(++cp);
                bcopy(cp, p->fts_name, len + 1);
                p->fts_namelen = len;
        }
        if ((cp = rindex(p->fts_name, '/')) && (cp != p->fts_name || cp[1])) {
                len = strlen(++cp);
                bcopy(cp, p->fts_name, len + 1);
                p->fts_namelen = len;
        }
-       p->fts_accpath = p->fts_path = stream->fts_path;
+       p->fts_accpath = p->fts_path = sp->fts_path;
+
+       p->fts_info = fts_stat(sp, p, 0);
+       sp->rdev = p->fts_statb.st_dev;
 }
 
 }
 
-ftsclose(sp)
+fts_close(sp)
        FTS *sp;
 {
        register FTSENT *freep, *p;
        FTS *sp;
 {
        register FTSENT *freep, *p;
@@ -180,89 +192,96 @@ ftsclose(sp)
 
        if (sp->fts_cur) {
                /*
 
        if (sp->fts_cur) {
                /*
-                * this still works if we haven't read anything -- the dummy
+                * This still works if we haven't read anything -- the dummy
                 * structure points to the root list, so we step through to
                 * the end of the root list which has a valid parent pointer.
                 */
                 * structure points to the root list, so we step through to
                 * the end of the root list which has a valid parent pointer.
                 */
-               for (p = sp->fts_cur; p->fts_level > ROOTPARENTLEVEL;) {
+               for (p = sp->fts_cur; p->fts_level > FTS_ROOTPARENTLEVEL;) {
                        freep = p;
                        p = p->fts_link ? p->fts_link : p->fts_parent;
                        freep = p;
                        p = p->fts_link ? p->fts_link : p->fts_parent;
-                       (void)free((void *)freep);
+                       free(freep);
                }
                }
-               (void)free((void *)p);
+               free(p);
        }
 
        }
 
-       /* free up child linked list, sort array, path buffer */
+       /* Free up child linked list, sort array, path buffer. */
        if (sp->fts_child)
                fts_lfree(sp->fts_child);
        if (sp->fts_array)
        if (sp->fts_child)
                fts_lfree(sp->fts_child);
        if (sp->fts_array)
-               (void)free((void *)sp->fts_array);
-       (void)free((char *)sp->fts_path);
+               free(sp->fts_array);
+       free(sp->fts_path);
 
 
-       /*
-        * return to original directory, save errno if necessary;
-        * free up the directory buffer
-        */
-       if (!(sp->fts_options & FTS_NOCHDIR)) {
-               saved_errno = fchdir(sp->fts_sd) ? errno : 0;
-               (void)close(sp->fts_sd);
+       /* Return to original directory, save errno if necessary. */
+       if (!ISSET(FTS_NOCHDIR)) {
+               saved_errno = fchdir(sp->fts_rfd) ? errno : 0;
+               (void)close(sp->fts_rfd);
        }
 
        }
 
-       /* free up the stream pointer */
-       (void)free((void *)sp);
+       /* Free up the stream pointer. */
+       free(sp);
 
 
-       /* set errno and return */
-       if (!(sp->fts_options & FTS_NOCHDIR) && saved_errno) {
+       /* Set errno and return. */
+       if (!ISSET(FTS_NOCHDIR) && saved_errno) {
                errno = saved_errno;
                return(-1);
        }
        return(0);
 }
 
                errno = saved_errno;
                return(-1);
        }
        return(0);
 }
 
+/*
+ * Special case a root of "/" so that slashes aren't appended causing
+ * paths to be written as "//foo".
+ */
+#define        NAPPEND(p) \
+       (p->fts_level == FTS_ROOTLEVEL && p->fts_pathlen == 1 && \
+           p->fts_path[0] == '/' ? 0 : p->fts_pathlen)
+
 FTSENT *
 FTSENT *
-ftsread(sp)
+fts_read(sp)
        register FTS *sp;
 {
        register FTSENT *p, *tmp;
        register int instr;
        register FTS *sp;
 {
        register FTSENT *p, *tmp;
        register int instr;
-       register char *cp;
-       static int cd;
+       register char *t;
 
 
-       /* if finished or unrecoverable error, return NULL */
-       if (!sp->fts_cur || sp->fts_options & FTS__STOP)
+       /* If finished or unrecoverable error, return NULL. */
+       if (!sp->fts_cur || ISSET(FTS_STOP))
                return(NULL);
 
                return(NULL);
 
-       /* set global stream pointer, and current node pointer */
-       stream = sp;
+       /* Set current node pointer. */
        p = sp->fts_cur;
 
        p = sp->fts_cur;
 
-       /* save and zero out user instructions */
+       /* Save and zero out user instructions. */
        instr = p->fts_instr;
        instr = p->fts_instr;
-       p->fts_instr = 0;
+       p->fts_instr = FTS_NOINSTR;
 
 
-       /* if used link pointer for cycle detection, restore it */
+       /* If used fts_link pointer for cycle detection, restore it. */
        if (sp->fts_savelink) {
                p->fts_link = sp->fts_savelink;
                sp->fts_savelink = NULL;
        }
 
        if (sp->fts_savelink) {
                p->fts_link = sp->fts_savelink;
                sp->fts_savelink = NULL;
        }
 
-       /* any type of file may be re-visited; re-stat and return */
+       /* Any type of file may be re-visited; re-stat and re-turn. */
        if (instr == FTS_AGAIN) {
        if (instr == FTS_AGAIN) {
-               p->fts_info = fts_stat(p, 0);
+               p->fts_info = fts_stat(sp, p, 0);
                return(p);
        }
 
                return(p);
        }
 
-       /* following a symbolic link */
-       if (p->fts_info == FTS_SL && instr == FTS_FOLLOW) {
-               p->fts_info = fts_stat(p, 1);
+       /*
+        * Following a symlink -- SLNONE test allows application to see
+        * SLNONE and recover.
+        */
+       if (instr == FTS_FOLLOW &&
+           (p->fts_info == FTS_SL || p->fts_info == FTS_SLNONE)) {
+               p->fts_info = fts_stat(sp, p, 1);
                return(p);
        }
 
                return(p);
        }
 
-       /* directory in pre-order */
+       /* Directory in pre-order. */
        if (p->fts_info == FTS_D) {
        if (p->fts_info == FTS_D) {
-               /* if skipped or crossed mount point, do post-order visit */
-               if (instr == FTS_SKIP || sp->fts_options & FTS_XDEV &&
-                   p->fts_statb.st_dev != sp->sdev) {
+               /* If skipped or crossed mount point, do post-order visit. */
+               if (instr == FTS_SKIP ||
+                   ISSET(FTS_XDEV) && p->fts_statb.st_dev != sp->rdev) {
                        if (sp->fts_child) {
                                fts_lfree(sp->fts_child);
                                sp->fts_child = NULL;
                        if (sp->fts_child) {
                                fts_lfree(sp->fts_child);
                                sp->fts_child = NULL;
@@ -271,91 +290,105 @@ ftsread(sp)
                        return(p);
                } 
 
                        return(p);
                } 
 
-               /* read the directory if necessary, and return first entry */
-               if (sp->fts_child) 
+               /*
+                * Cd to the subdirectory, reading it if haven't already.  If
+                * the read fails for any reason, or the directory is empty,
+                * the fts_info field of the current node is set by fts_build.
+                * If have already read and now fail to chdir, whack the list
+                * to make the names come out right, and set the parent state
+                * so the application will eventually get an error condition.
+                * If haven't read and fail to chdir, check to see if we're
+                * at the root node -- if so, we have to get back or the root
+                * node may be inaccessible.
+                */
+               if (sp->fts_child) {
                        if (CHDIR(sp, p->fts_accpath)) {
                        if (CHDIR(sp, p->fts_accpath)) {
-                               fts_lfree(sp->fts_child);
-                               p->fts_info = FTS_DNX;
-                       } else {
-                               p = sp->fts_child;
-                               cp = sp->fts_path + NAPPEND(p->fts_parent);
-                               *cp++ = '/';
-                               bcopy(p->fts_name, cp, p->fts_namelen + 1);
+                               p->fts_parent->fts_cderr = errno;
+                               for (p = sp->fts_child; p; p = p->fts_link)
+                                       p->fts_accpath =
+                                           p->fts_parent->fts_accpath;
                        }
                        }
-               else {
-                       if (!(sp->fts_child = fts_build(sp, BREAD)))
-                               return(p);
-                       p = sp->fts_child;
+               } else if (!(sp->fts_child = fts_build(sp, BREAD))) {
+                       if ISSET(FTS_STOP)
+                               return(NULL);
+                       if (p->fts_level == FTS_ROOTLEVEL &&
+                           FCHDIR(sp, sp->fts_rfd)) {
+                               SET(FTS_STOP);
+                               return(NULL);
+                       }
+                       return(p);
                }
                }
+               p = sp->fts_child;
                sp->fts_child = NULL;
                sp->fts_child = NULL;
-               return(sp->fts_cur = p);
+               goto name;
        }
 
        }
 
-       /* move to next node on this level */
+       /* Move to next node on this level. */
 next:  tmp = p;
        if (p = p->fts_link) {
 next:  tmp = p;
        if (p = p->fts_link) {
-               /*
-                * if root level node, set up paths and return.  If not the
-                * first time, and it's not an absolute pathname, get back
-                * to starting directory.
-                */
-               if (p->fts_level == ROOTLEVEL) {
-                       fts_load(p);
-                       (void)free((void *)tmp);
-                       if (cd &&
-                           p->fts_path[0] != '/' && FCHDIR(sp, sp->fts_sd)) {
-                               /* should never happen... */
-                               p->fts_path = "starting directory";
-                               p->fts_info = FTS_ERR;
-                               sp->fts_options |= FTS__STOP;
-                               return(sp->fts_cur = p);
-                       } 
-                       cd = 1;
-                       p->fts_info = fts_stat(p, 0);
-                       sp->sdev = p->fts_statb.st_dev;
-               } else {
-                       (void)free((void *)tmp);
-
-                       /* user may have called ftsset on node */
-                       if (p->fts_instr == FTS_SKIP)
-                               goto next;
-                       if (p->fts_instr == FTS_FOLLOW) {
-                               p->fts_info = fts_stat(p, 1);
-                               p->fts_instr = 0;
-                       }
+               free(tmp);
 
 
-                       /* fill in the paths */
-                       cp = sp->fts_path + NAPPEND(p->fts_parent);
-                       *cp++ = '/';
-                       bcopy(p->fts_name, cp, p->fts_namelen + 1);
+               /* If reached the top, load the paths for the next root. */
+               if (p->fts_level == FTS_ROOTLEVEL) {
+                       fts_load(sp, p);
+                       return(sp->fts_cur = p);
+               }
 
 
-                       /* check for directory cycles */
-                       if (p->fts_info == FTS_D && (tmp = fts_cycle(p))) {
-                               sp->fts_savelink = p->fts_link;
-                               p->fts_link = tmp;
-                               p->fts_info = FTS_DC;
-                       }
+               /* User may have called fts_set on the node. */
+               if (p->fts_instr == FTS_SKIP)
+                       goto next;
+               if (p->fts_instr == FTS_FOLLOW) {
+                       p->fts_info = fts_stat(sp, p, 1);
+                       p->fts_instr = FTS_NOINSTR;
                }
                }
+
+name:          t = sp->fts_path + NAPPEND(p->fts_parent);
+               *t++ = '/';
+               bcopy(p->fts_name, t, p->fts_namelen + 1);
                return(sp->fts_cur = p);
        }
 
                return(sp->fts_cur = p);
        }
 
-       /* move to parent */
+       /* Move up to the parent node. */
        p = tmp->fts_parent;
        p = tmp->fts_parent;
-       (void)free((void *)tmp);
+       free(tmp);
 
 
-       if (p->fts_level == ROOTPARENTLEVEL) {
+       if (p->fts_level == FTS_ROOTPARENTLEVEL) {
                /*
                /*
-                * done; free everything up and set errno to 0 so the user
+                * Done; free everything up and set errno to 0 so the user
                 * can distinguish between error and EOF.
                 */
                 * can distinguish between error and EOF.
                 */
-               (void)free((void *)p);
+               free(p);
                errno = 0;
                return(sp->fts_cur = NULL);
        }
 
        sp->fts_path[p->fts_pathlen] = '\0';
                errno = 0;
                return(sp->fts_cur = NULL);
        }
 
        sp->fts_path[p->fts_pathlen] = '\0';
-       if (CHDIR(sp, "..")) {
-               sp->fts_options |= FTS__STOP;
+
+       /*
+        * Cd back up to the parent directory.  If at a root node, have to cd
+        * back to the original place, otherwise may not be able to access the
+        * original node on post-order.
+        */
+       if (p->fts_level == FTS_ROOTLEVEL) {
+               if (FCHDIR(sp, sp->fts_rfd)) {
+                       SET(FTS_STOP);
+                       return(NULL);
+               }
+       }
+       else if (CHDIR(sp, "..")) {
+               SET(FTS_STOP);
+               return(NULL);
+       }
+
+       /* 
+        * If had a chdir error when trying to get into the directory, set the
+        * info field to reflect this, and restore errno.  The error indicator
+        * has to be reset to 0 so that if the user does an FTS_AGAIN, it all
+        * works.
+        */
+       if (p->fts_cderr) {
+               errno = p->fts_cderr;
+               p->fts_cderr = 0;
                p->fts_info = FTS_ERR;
        } else
                p->fts_info = FTS_DP;
                p->fts_info = FTS_ERR;
        } else
                p->fts_info = FTS_DP;
@@ -363,13 +396,13 @@ next:     tmp = p;
 }
 
 /*
 }
 
 /*
- * ftsset takes the stream as an argument although it's not used in this
+ * Fts_set takes the stream as an argument although it's not used in this
  * implementation; it would be necessary if anyone wanted to add global
  * implementation; it would be necessary if anyone wanted to add global
- * semantics to fts using ftsset.  A possible error return is allowed for
- * similar reasons.
+ * semantics to fts using fts_set.  An error return is allowed for similar
+ * reasons.
  */
 /* ARGSUSED */
  */
 /* ARGSUSED */
-ftsset(sp, p, instr)
+fts_set(sp, p, instr)
        FTS *sp;
        FTSENT *p;
        int instr;
        FTS *sp;
        FTSENT *p;
        int instr;
@@ -379,31 +412,38 @@ ftsset(sp, p, instr)
 }
 
 FTSENT *
 }
 
 FTSENT *
-ftschildren(sp)
+fts_children(sp)
        register FTS *sp;
 {
        register FTSENT *p;
        int fd;
 
        register FTS *sp;
 {
        register FTSENT *p;
        int fd;
 
+       /* Set current node pointer. */
+       p = sp->fts_cur;
+
        /*
        /*
-        * set errno to 0 so that user can tell the difference between an
-        * error and a directory without entries.
+        * Set errno to 0 so that user can tell the difference between an
+        * error and a directory without entries.  If not a directory being
+        * visited in *pre-order*, or we've already had fatal errors, return
+        * immediately.
         */
        errno = 0;
         */
        errno = 0;
-       p = sp->fts_cur;
-       if (p->fts_info != FTS_D || sp->fts_options & FTS__STOP)
+       if (ISSET(FTS_STOP) || p->fts_info != FTS_D && p->fts_info != FTS_DNR)
                return(NULL);
                return(NULL);
+
+       /* Free up any previous child list. */
        if (sp->fts_child)
                fts_lfree(sp->fts_child);
 
        /*
        if (sp->fts_child)
                fts_lfree(sp->fts_child);
 
        /*
-        * if using chdir on a relative path and called BEFORE ftsread on the
-        * root of a traversal, we can lose because we need to chdir into the
-        * subdirectory, and we don't know where the current directory is to
-        * get back so that the upcoming chdir by ftsread will work.
+        * If using chdir on a relative path and called BEFORE fts_read does
+        * its chdir to the root of a traversal, we can lose -- we need to
+        * chdir into the subdirectory, and we don't know where the current
+        * directory is, so we can't get back so that the upcoming chdir by
+        * fts_read will work.
         */
         */
-       if (p->fts_level != ROOTLEVEL || p->fts_accpath[0] == '/' ||
-           sp->fts_options & FTS_NOCHDIR)
+       if (p->fts_level != FTS_ROOTLEVEL || p->fts_accpath[0] == '/' ||
+           ISSET(FTS_NOCHDIR))
                return(sp->fts_child = fts_build(sp, BCHILD));
 
        if ((fd = open(".", O_RDONLY, 0)) < 0)
                return(sp->fts_child = fts_build(sp, BCHILD));
 
        if ((fd = open(".", O_RDONLY, 0)) < 0)
@@ -415,9 +455,21 @@ ftschildren(sp)
        return(sp->fts_child);
 }
 
        return(sp->fts_child);
 }
 
+/*
+ * This is the tricky part -- do not casually change *anything* in here.  The
+ * idea is to build the linked list of entries that are used by fts_children
+ * and fts_read.  There are lots of special cases.
+ *
+ * The real slowdown in walking the tree is the stat calls.  If FTS_NOSTAT is
+ * set and it's a physical walk (so that symbolic links can't be directories),
+ * we assume that the number of subdirectories in a node is equal to the number
+ * of links to the parent.  This allows stat calls to be skipped in any leaf
+ * directories and for any nodes after the directories in the parent node have
+ * been found.  This empirically cuts the stat calls by about 2/3.
+ */
 #define        ISDOT(a)        (a[0] == '.' && (!a[1] || a[1] == '.' && !a[2]))
 
 #define        ISDOT(a)        (a[0] == '.' && (!a[1] || a[1] == '.' && !a[2]))
 
-FTSENT *
+static FTSENT *
 fts_build(sp, type)
        register FTS *sp;
        int type;
 fts_build(sp, type)
        register FTS *sp;
        int type;
@@ -425,101 +477,126 @@ fts_build(sp, type)
        register struct dirent *dp;
        register FTSENT *p, *head;
        register int nitems;
        register struct dirent *dp;
        register FTSENT *p, *head;
        register int nitems;
+       FTSENT *cur;
        DIR *dirp;
        DIR *dirp;
-       int descend, len, level, maxlen, nlinks, saved_errno;
+       int cderr, descend, len, level, maxlen, nlinks, saved_errno;
        char *cp;
 
        char *cp;
 
-       p = sp->fts_cur;
-       if (!(dirp = opendir(p->fts_accpath))) {
-               if (type == BREAD) {
-                       errno = 0;
-                       p->fts_info = FTS_DNR;
-               }
+       /* Set current node pointer. */
+       cur = sp->fts_cur;
+
+       /*
+        * Open the directory for reading.  If this fails, we're done.
+        * If being called from fts_read, set the fts_info field.
+        */
+       if (!(dirp = opendir(cur->fts_accpath))) {
+               if (type == BREAD)
+                       cur->fts_info = FTS_DNR;
                return(NULL);
        }
 
        /*
                return(NULL);
        }
 
        /*
-        * the real slowdown in walking the tree is the stat calls.  If
-        * FTS_NOSTAT is set and it's a physical walk (so that symbolic
-        * links can't be directories), fts assumes that the number of
-        * subdirectories in a node is equal to the number of links to
-        * the parent.  This allows stat calls to be skipped in any leaf
-        * directories and for any nodes after the directories in the
-        * parent node have been found.  This empirically cuts the stat
-        * calls by about 2/3.
+        * Nlinks is the number of possible entries of type directory in the
+        * directory if we're cheating on stat calls, 0 if we're not doing
+        * any stat calls at all, -1 if we're doing stats on everything.
         */
        nlinks =
         */
        nlinks =
-           sp->fts_options & FTS_NOSTAT && sp->fts_options & FTS_PHYSICAL ?
-           p->fts_statb.st_nlink - (sp->fts_options & FTS_SEEDOT ? 0 : 2) : -1;
+           ISSET(FTS_NOSTAT) && ISSET(FTS_PHYSICAL) ?
+           cur->fts_statb.st_nlink - (ISSET(FTS_SEEDOT) ? 0 : 2) : -1;
 
 
-       /* if told to descend or found links and not told not to descend. */
-       if (nlinks || type == BREAD) {
+       /*
+        * If we're going to need to stat anything or we want to descend
+        * and stay in the directory, chdir.  If this fails we keep going.
+        * We won't be able to stat anything, but we can still return the
+        * names themselves.  Note, that since fts_read won't be able to
+        * chdir into the directory, it will have to return different path
+        * names than before, i.e. "a/b" instead of "b".  Since the node
+        * has already been visited in pre-order, have to wait until the
+        * post-order visit to return the error.  This is all fairly nasty.
+        * If a program needed sorted entries or stat information, they had
+        * better be checking FTS_NS on the returned nodes.
+        */
+       if (nlinks || type == BREAD)
                if (FCHDIR(sp, dirfd(dirp))) {
                if (FCHDIR(sp, dirfd(dirp))) {
-                       if (type == BREAD) {
-                               errno = 0;
-                               p->fts_info = FTS_DNX;
-                       }
-                       (void)closedir(dirp);
-                       return(NULL);
+                       if (type == BREAD)
+                               cur->fts_cderr = errno;
+                       descend = nlinks = 0;
+                       cderr = 1;
+               } else {
+                       descend = 1;
+                       cderr = 0;
                }
                }
-               descend = 1;
-       } else
+       else
                descend = 0;
 
                descend = 0;
 
-       /* get max file name length that can be stored in current path */
-       maxlen = sp->fts_pathlen - p->fts_pathlen - 1;
-
-       cp = sp->fts_path + (len = NAPPEND(p));
-       *cp++ = '/';
+       /*
+        * Figure out the max file name length that can be stored in the
+        * current path -- the inner loop allocates more path as necessary.
+        * We really wouldn't have to do the maxlen calculations here, we
+        * could do them in fts_read before returning the path, but it's a
+        * lot easier here since the length is part of the dirent structure.
+        *
+        * If not changing directories set a pointer so that we can just
+        * append each new name into the path.
+        */
+       maxlen = sp->fts_pathlen - cur->fts_pathlen - 1;
+       len = NAPPEND(cur);
+       if (ISSET(FTS_NOCHDIR)) {
+               cp = sp->fts_path + len;
+               *cp++ = '/';
+       }
 
 
-       level = p->fts_level + 1;
+       level = cur->fts_level + 1;
 
 
-       /* read the directory, attching each new entry to the `link' pointer */
+       /* Read the directory, attaching each entry to the `link' pointer. */
        for (head = NULL, nitems = 0; dp = readdir(dirp);) {
        for (head = NULL, nitems = 0; dp = readdir(dirp);) {
-               if (ISDOT(dp->d_name) && !(sp->fts_options & FTS_SEEDOT))
+               if (!ISSET(FTS_SEEDOT) && ISDOT(dp->d_name))
                        continue;
 
                        continue;
 
-               if (!(p = fts_alloc(dp->d_name, dp->d_namlen))) {
-                       saved_errno = errno;
+               if (!(p = fts_alloc(sp, dp->d_name, (int)dp->d_namlen)))
                        goto mem1;
                        goto mem1;
-               }
                if (dp->d_namlen > maxlen) {
                if (dp->d_namlen > maxlen) {
-                       if (!fts_path((int)dp->d_namlen)) {
-                               /* quit: this stream no longer has a path */
-                               sp->fts_options |= FTS__STOP;
-                               saved_errno = errno;
-                               (void)free((void *)p);
-mem1:                          fts_lfree(head);
-                               if (type == BREAD)
-                                       p->fts_info = FTS_ERR;
-                               if (descend && CHDIR(sp, "..")) {
-                                       saved_errno = errno;
-                                       sp->fts_options |= FTS__STOP;
-                               }
-                               errno = saved_errno;
+                       if (!fts_path(sp, (int)dp->d_namlen)) {
+                               /*
+                                * No more memory for path or structures.  Save
+                                * errno, free up the current structure and the
+                                * structures already allocated.
+                                */
+mem1:                          saved_errno = errno;
+                               if (p)
+                                       free(p);
+                               fts_lfree(head);
                                (void)closedir(dirp);
                                (void)closedir(dirp);
+                               errno = saved_errno;
+                               cur->fts_info = FTS_ERR;
+                               SET(FTS_STOP);
                                return(NULL);
                        }
                        maxlen = sp->fts_pathlen - sp->fts_cur->fts_pathlen - 1;
                }
 
                p->fts_pathlen = len + dp->d_namlen + 1;
                                return(NULL);
                        }
                        maxlen = sp->fts_pathlen - sp->fts_cur->fts_pathlen - 1;
                }
 
                p->fts_pathlen = len + dp->d_namlen + 1;
-               p->fts_accpath =
-                   sp->fts_options & FTS_NOCHDIR ? p->fts_path : p->fts_name;
-
                p->fts_parent = sp->fts_cur;
                p->fts_level = level;
 
                if (nlinks) {
                p->fts_parent = sp->fts_cur;
                p->fts_level = level;
 
                if (nlinks) {
-                       /* make sure fts_stat has a filename to stat */
-                       if (sp->fts_options & FTS_NOCHDIR)
+                       /* Build a file name for fts_stat to stat. */
+                       if (ISSET(FTS_NOCHDIR)) {
+                               p->fts_accpath = p->fts_path;
                                bcopy(p->fts_name, cp, p->fts_namelen + 1);
                                bcopy(p->fts_name, cp, p->fts_namelen + 1);
-                       p->fts_info = fts_stat(p, 0);
-                       if (nlinks > 0 && (p->fts_info == FTS_D ||
-                           p->fts_info == FTS_DNR || p->fts_info == FTS_DNX))
+                       } else
+                               p->fts_accpath = p->fts_name;
+                       p->fts_info = fts_stat(sp, p, 0);
+                       if (nlinks > 0 && p->fts_info == FTS_D)
                                --nlinks;
                                --nlinks;
-               } else
-                       p->fts_info = FTS_NS;
+               } else if (cderr) {
+                       p->fts_info = ISSET(FTS_NOSTAT) ? FTS_NSOK : FTS_NS;
+                       p->fts_accpath = cur->fts_accpath;
+               } else {
+                       p->fts_accpath =
+                           ISSET(FTS_NOCHDIR) ? p->fts_path : p->fts_name;
+                       p->fts_info = FTS_NSOK;
+               }
 
                p->fts_link = head;
                head = p;
 
                p->fts_link = head;
                head = p;
@@ -527,188 +604,184 @@ mem1:                           fts_lfree(head);
        }
        (void)closedir(dirp);
 
        }
        (void)closedir(dirp);
 
-       /* reset the path */
-       if (cp - 1 > sp->fts_path)
-               --cp;
-       *cp = '\0';
+       /*
+        * If not changing directories, reset the path back to original
+        * state.
+        */
+       if (ISSET(FTS_NOCHDIR)) {
+               if (cp - 1 > sp->fts_path)
+                       --cp;
+               *cp = '\0';
+       }
 
        /*
 
        /*
-        * if descended: if were called from ftsread and didn't find anything,
-        * or were called from ftschildren, get back.
+        * If descended after called from fts_children or called from
+        * fts_read and didn't find anything, get back.  If can't get
+        * back, we're done.
         */
        if (descend && (!nitems || type == BCHILD) && CHDIR(sp, "..")) {
         */
        if (descend && (!nitems || type == BCHILD) && CHDIR(sp, "..")) {
-               sp->fts_options |= FTS__STOP;
-               p->fts_info = FTS_ERR;
+               cur->fts_info = FTS_ERR;
+               SET(FTS_STOP);
                return(NULL);
        }
 
                return(NULL);
        }
 
+       /* If we didn't find anything, just do the post-order visit */
        if (!nitems) {
                if (type == BREAD)
        if (!nitems) {
                if (type == BREAD)
-                       p->fts_info = FTS_DP;
+                       cur->fts_info = FTS_DP;
                return(NULL);
        }
 
                return(NULL);
        }
 
+       /* Sort the entries. */
        if (sp->fts_compar && nitems > 1)
        if (sp->fts_compar && nitems > 1)
-               head = fts_sort(head, nitems);
-
-       if (type == BREAD) {
-               *cp = '/';
-               bcopy(head->fts_name, cp + 1, head->fts_namelen + 1);
-       }
+               head = fts_sort(sp, head, nitems);
        return(head);
 }
 
        return(head);
 }
 
-static short
-fts_stat(p, symflag)
+static u_short
+fts_stat(sp, p, follow)
+       FTS *sp;
        register FTSENT *p;
        register FTSENT *p;
-       int symflag;
+       int follow;
 {
 {
+       int saved_errno;
+
        /*
        /*
-        * detection of symbolic links w/o targets.  If FTS_FOLLOW is set,
-        * the symlink structure is overwritten with the stat structure of
-        * the target.
+        * If doing a logical walk, or application requested FTS_FOLLOW, do
+        * a stat(2).  If that fails, check for a non-existent symlink.  If
+        * fail, return the errno from the stat call.
         */
         */
-       if (stream->fts_options & FTS_LOGICAL || symflag) {
-               if (stat(p->fts_accpath, &p->fts_statb))
-                       return(symflag || !lstat(p->fts_accpath,
-                           &p->fts_statb) ? FTS_SLNONE : FTS_ERR);
-       } else if (lstat(p->fts_accpath, &p->fts_statb))
-               return(FTS_ERR);
-
-       switch(p->fts_statb.st_mode&S_IFMT) {
-       case S_IFDIR:
-               return(FTS_D);
-       case S_IFLNK:
-               return(FTS_SL);
-       case S_IFREG:
-               return(FTS_F);
+       if (ISSET(FTS_LOGICAL) || follow) {
+               if (stat(p->fts_accpath, &p->fts_statb)) {
+                       saved_errno = errno;
+                       if (!lstat(p->fts_accpath, &p->fts_statb)) {
+                               errno = 0;
+                               return(FTS_SLNONE);
+                       } 
+                       errno = saved_errno;
+                       bzero(&p->fts_statb, sizeof(struct stat));
+                       return(FTS_NS);
+               }
+       } else if (lstat(p->fts_accpath, &p->fts_statb)) {
+               bzero(&p->fts_statb, sizeof(struct stat));
+               return(FTS_NS);
        }
        }
-       return(FTS_DEFAULT);
-}
-
-static FTSENT *
-fts_cycle(p)
-       register FTSENT *p;
-{
-       register dev_t dev;
-       register ino_t ino;
 
        /*
 
        /*
-        * cycle detection is brute force; if the tree gets deep enough or
-        * the number of symbolic links to directories is really high
-        * something faster might be worthwhile.
+        * Cycle detection is done as soon as we find a directory.  Detection
+        * is by brute force; if the tree gets deep enough or the number of
+        * symbolic links to directories high enough something faster might
+        * be worthwhile.
         */
         */
-       dev = p->fts_statb.st_dev;
-       ino = p->fts_statb.st_ino;
-       for (p = p->fts_parent; p->fts_level > ROOTLEVEL; p = p->fts_parent)
-               if (ino == p->fts_statb.st_ino && dev == p->fts_statb.st_dev)
-                       return(p);
-       return(NULL);
+       if (S_ISDIR(p->fts_statb.st_mode)) {
+               register FTSENT *t;
+               register dev_t dev;
+               register ino_t ino;
+
+               dev = p->fts_statb.st_dev;
+               ino = p->fts_statb.st_ino;
+               for (t = p->fts_parent; t->fts_level > FTS_ROOTLEVEL;
+                   t = t->fts_parent)
+                       if (ino == t->fts_statb.st_ino &&
+                           dev == t->fts_statb.st_dev) {
+                               sp->fts_savelink = p->fts_link;
+                               p->fts_link = t;
+                               return(FTS_DC);
+                       }
+               return(FTS_D);
+       }
+       if (S_ISLNK(p->fts_statb.st_mode))
+               return(FTS_SL);
+       if (S_ISREG(p->fts_statb.st_mode))
+               return(FTS_F);
+       return(FTS_DEFAULT);
 }
 
 #define        R(type, nelem, ptr) \
        (type *)realloc((void *)ptr, (u_int)((nelem) * sizeof(type)))
 
 static FTSENT *
 }
 
 #define        R(type, nelem, ptr) \
        (type *)realloc((void *)ptr, (u_int)((nelem) * sizeof(type)))
 
 static FTSENT *
-fts_sort(head, nitems)
+fts_sort(sp, head, nitems)
+       FTS *sp;
        FTSENT *head;
        register int nitems;
 {
        register FTSENT **ap, *p;
 
        /*
        FTSENT *head;
        register int nitems;
 {
        register FTSENT **ap, *p;
 
        /*
-        * construct an array of pointers to the structures and call qsort(3).
+        * Construct an array of pointers to the structures and call qsort(3).
         * Reassemble the array in the order returned by qsort.  If unable to
         * sort for memory reasons, return the directory entries in their
         * current order.  Allocate enough space for the current needs plus
         * 40 so we don't realloc one entry at a time.
         */
         * Reassemble the array in the order returned by qsort.  If unable to
         * sort for memory reasons, return the directory entries in their
         * current order.  Allocate enough space for the current needs plus
         * 40 so we don't realloc one entry at a time.
         */
-       if (nitems > stream->fts_nitems) {
-               stream->fts_nitems = nitems + 40;
-               if (!(stream->fts_array =
-                   R(FTSENT *, stream->fts_nitems, stream->fts_array))) {
-                       stream->fts_nitems = 0;
+       if (nitems > sp->fts_nitems) {
+               sp->fts_nitems = nitems + 40;
+               if (!(sp->fts_array =
+                   R(FTSENT *, sp->fts_nitems, sp->fts_array))) {
+                       sp->fts_nitems = 0;
                        return(head);
                }
        }
                        return(head);
                }
        }
-       for (ap = stream->fts_array, p = head; p; p = p->fts_link)
+       for (ap = sp->fts_array, p = head; p; p = p->fts_link)
                *ap++ = p;
                *ap++ = p;
-       qsort((void *)stream->fts_array, nitems, sizeof(FTSENT *),
-           stream->fts_compar);
-       for (head = *(ap = stream->fts_array); --nitems; ++ap)
+       qsort((void *)sp->fts_array, nitems, sizeof(FTSENT *), sp->fts_compar);
+       for (head = *(ap = sp->fts_array); --nitems; ++ap)
                ap[0]->fts_link = ap[1];
        ap[0]->fts_link = NULL;
        return(head);
 }
 
 static FTSENT *
                ap[0]->fts_link = ap[1];
        ap[0]->fts_link = NULL;
        return(head);
 }
 
 static FTSENT *
-fts_alloc(name, len)
+fts_alloc(sp, name, len)
+       FTS *sp;
        char *name;
        register int len;
 {
        register FTSENT *p;
 
        /*
        char *name;
        register int len;
 {
        register FTSENT *p;
 
        /*
-        * variable sized structures; the name is the last element so
-        * allocate enough extra space after the structure to hold it.
+        * Variable sized structures; the name is the last element so
+        * we allocate enough extra space after the structure to store
+        * it.
         */
        if (!(p = (FTSENT *)malloc((size_t)(sizeof(FTSENT) + len))))
                return(NULL);
        bcopy(name, p->fts_name, len + 1);
        p->fts_namelen = len;
         */
        if (!(p = (FTSENT *)malloc((size_t)(sizeof(FTSENT) + len))))
                return(NULL);
        bcopy(name, p->fts_name, len + 1);
        p->fts_namelen = len;
-       p->fts_path = stream->fts_path;
-       p->fts_instr = 0;
-       p->fts_local.number = 0;
-       p->fts_local.pointer = NULL;
+       p->fts_path = sp->fts_path;
+       p->fts_instr = FTS_NOINSTR;
+       p->fts_cderr = 0;
+       p->fts_number = 0;
+       p->fts_pointer = NULL;
        return(p);
 }
 
        return(p);
 }
 
-static
+static void
 fts_lfree(head)
        register FTSENT *head;
 {
        register FTSENT *p;
 
 fts_lfree(head)
        register FTSENT *head;
 {
        register FTSENT *p;
 
+       /* Free a linked list of structures. */
        while (p = head) {
                head = head->fts_link;
        while (p = head) {
                head = head->fts_link;
-               (void)free((void *)p);
+               free(p);
        }
 }
 
 /*
        }
 }
 
 /*
- * allow essentially unlimited paths; certain programs (find, remove, ls)
- * need to work on any tree.  Most systems will allow creation of paths
- * much longer than MAXPATHLEN, even though the kernel won't resolve them.
- * Add an extra 128 bytes to the requested size so that we don't realloc
- * the path 2 bytes at a time.
+ * Allow essentially unlimited paths; certain programs (find, rm, ls) need to
+ * work on any tree.  Most systems will allow creation of paths much longer
+ * than MAXPATHLEN, even though the kernel won't resolve them.  Add an extra
+ * 128 bytes to the requested size so that we don't realloc the path 2 bytes
+ * at a time.
  */
 static char *
  */
 static char *
-fts_path(size)
+fts_path(sp, size)
+       FTS *sp;
        int size;
 {
        int size;
 {
-       stream->fts_pathlen += size + 128;
-       return(stream->fts_path =
-           R(char, stream->fts_pathlen, stream->fts_path));
-}
-
-static FTSENT *
-fts_root(name)
-       register char *name;
-{
-       register char *cp;
-
-       /*
-        * rip trailing slashes; it's somewhat unclear in POSIX 1003.1 what
-        * /a/b/ really is, they don't talk about what a null path component
-        * resolves to.  This hopefully does what the user intended.  Don't
-        * allow null pathnames.
-        */
-       for (cp = name; *cp; ++cp);
-       if (cp == name) {
-               errno = ENOENT;
-               return(NULL);
-       }
-       while (--cp > name && *cp == '/');
-       *++cp = '\0';
-       return(fts_alloc(name, cp - name));
+       sp->fts_pathlen += size + 128;
+       return(sp->fts_path = R(char, sp->fts_pathlen, sp->fts_path));
 }
 }