gawk-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[gawk-diffs] [SCM] gawk branch, master, updated. 17edae8fbae125b54e85c2b


From: Arnold Robbins
Subject: [gawk-diffs] [SCM] gawk branch, master, updated. 17edae8fbae125b54e85c2be94f198f228465f81
Date: Wed, 29 Aug 2012 19:38:50 +0000

This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "gawk".

The branch, master has been updated
       via  17edae8fbae125b54e85c2be94f198f228465f81 (commit)
      from  8b4c4f85fbbc5282702b0367c04d1275b62dd6ef (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
http://git.sv.gnu.org/cgit/gawk.git/commit/?id=17edae8fbae125b54e85c2be94f198f228465f81

commit 17edae8fbae125b54e85c2be94f198f228465f81
Author: Arnold D. Robbins <address@hidden>
Date:   Wed Aug 29 22:38:15 2012 +0300

    More wonderful portability stuff in the extensions.

diff --git a/extension/ChangeLog b/extension/ChangeLog
index 466ebd6..899f72d 100644
--- a/extension/ChangeLog
+++ b/extension/ChangeLog
@@ -1,3 +1,12 @@
+2012-08-29         Arnold D. Robbins     <address@hidden>
+
+       Make fts work everywhere by using our own source.
+
+       * README.fts, gawkfts.c, gawkfts.h, fts.3: New files.
+       * Makefile.am (filefuncs_la_SOURCES, EXTRA_DIST): Adjust.
+       * configure.ac: Remove check for fts.h and fts_XXX functions.
+       * filefuncs.c: Remove various ifdefs, change includes around.
+
 2012-08-28         Andrew J. Schorr     <address@hidden>
 
        * Makefile.am: Rename man_MANS to dist_man_MANS to include the man
diff --git a/extension/Makefile.am b/extension/Makefile.am
index 1bd0bda..445eac3 100644
--- a/extension/Makefile.am
+++ b/extension/Makefile.am
@@ -48,7 +48,7 @@ MY_MODULE_FLAGS = -module -avoid-version -no-undefined
 # on Cygwin, gettext requires that we link with -lintl 
 MY_LIBS = $(LIBINTL)
 
-filefuncs_la_SOURCES  = filefuncs.c stack.h stack.c
+filefuncs_la_SOURCES  = filefuncs.c stack.h stack.c gawkfts.h gawkfts.c
 filefuncs_la_LDFLAGS  = $(MY_MODULE_FLAGS)
 filefuncs_la_LIBADD   = $(MY_LIBS)
 
@@ -94,7 +94,8 @@ testext_la_LIBADD     = $(MY_LIBS)
 
 EXTRA_DIST = build-aux/config.rpath  \
        ChangeLog \
-       ChangeLog.0
+       ChangeLog.0 \
+       fts.3
 
 dist_man_MANS = \
        filefuncs.3am fnmatch.3am fork.3am ordchr.3am \
diff --git a/extension/Makefile.in b/extension/Makefile.in
index a217b58..687c496 100644
--- a/extension/Makefile.in
+++ b/extension/Makefile.in
@@ -138,7 +138,7 @@ LTLIBRARIES = $(pkgextension_LTLIBRARIES)
 am__DEPENDENCIES_1 =
 am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1)
 filefuncs_la_DEPENDENCIES = $(am__DEPENDENCIES_2)
-am_filefuncs_la_OBJECTS = filefuncs.lo stack.lo
+am_filefuncs_la_OBJECTS = filefuncs.lo stack.lo gawkfts.lo
 filefuncs_la_OBJECTS = $(am_filefuncs_la_OBJECTS)
 filefuncs_la_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) \
        $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
@@ -446,7 +446,7 @@ pkgextension_LTLIBRARIES = \
 MY_MODULE_FLAGS = -module -avoid-version -no-undefined
 # on Cygwin, gettext requires that we link with -lintl 
 MY_LIBS = $(LIBINTL)
-filefuncs_la_SOURCES = filefuncs.c stack.h stack.c
+filefuncs_la_SOURCES = filefuncs.c stack.h stack.c gawkfts.h gawkfts.c
 filefuncs_la_LDFLAGS = $(MY_MODULE_FLAGS)
 filefuncs_la_LIBADD = $(MY_LIBS)
 fnmatch_la_SOURCES = fnmatch.c
@@ -481,7 +481,8 @@ testext_la_LDFLAGS = $(MY_MODULE_FLAGS)
 testext_la_LIBADD = $(MY_LIBS)
 EXTRA_DIST = build-aux/config.rpath  \
        ChangeLog \
-       ChangeLog.0
+       ChangeLog.0 \
+       fts.3
 
 dist_man_MANS = \
        filefuncs.3am fnmatch.3am fork.3am ordchr.3am \
@@ -610,6 +611,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @address@hidden/$(DEPDIR)/address@hidden@
 @AMDEP_TRUE@@am__include@ @address@hidden/$(DEPDIR)/address@hidden@
 @AMDEP_TRUE@@am__include@ @address@hidden/$(DEPDIR)/address@hidden@
address@hidden@@am__include@ @address@hidden/$(DEPDIR)/address@hidden@
 @AMDEP_TRUE@@am__include@ @address@hidden/$(DEPDIR)/address@hidden@
 @AMDEP_TRUE@@am__include@ @address@hidden/$(DEPDIR)/address@hidden@
 @AMDEP_TRUE@@am__include@ @address@hidden/$(DEPDIR)/address@hidden@
diff --git a/extension/README.fts b/extension/README.fts
new file mode 100644
index 0000000..b8b948c
--- /dev/null
+++ b/extension/README.fts
@@ -0,0 +1,68 @@
+Wed Aug 29 22:34:24 IDT 2012
+============================
+
+Portability is a myth, a chimera. Like the pot of gold at the end of
+the rainbow, it's always looks like it is just within your reach, but
+in fact, it is seldom, if ever, truly acheivable.
+
+It all started when trying to get the extensions to work on my aged
+PowerPC Macbook G4 running Mac OS X 10.5.  That machine is wonderful,
+since it is neither an Intel architecture machine nor is it GNU/Linux;
+two criteria that are becoming increasingly harder to meet over 10%
+into the 21st century.
+
+The readdir extension didn't work. Furthermore, GDB showed that in gawk
+itself, struct stat had one size and layout, while inside the readdir
+code, it had another.
+
+This turned out to be due to the large file nonsense foisted off upon
+us by POSIX. The main gawk configure.ac had AC_SYS_LARGEFILE but the
+extensions' configure.ac did not.  OK - a one line fix, rebuild, and
+everything is OK.  Right?
+
+Of course not.
+
+Everything was OK on my 64-bit GNU/Linux Intel system, but lo and behold,
+I compiled on a 32-bit GNU/Linux system and suddenly the fts tests
+stopped working. A look at the config.log reveals:
+
+       <fts.h> cannot be used with -D_FILE_OFFSET_BITS==64
+
+Why? Who knows. The comment in the file itself is cryptic:
+
+       /* The fts interface is incompatible with the LFS interface which
+          transparently uses the 64-bit file access functions.  */
+       #ifdef __USE_FILE_OFFSET64
+       # error "<fts.h> cannot be used with -D_FILE_OFFSET_BITS==64"
+       #endif
+
+It purports to be English, but says nothing intelligible, and the man
+page sheds no light, either.
+
+Fortunately, the source for fts from NetBSD is reasonably legible,
+freely available, and has a usable license.
+
+So, I try to just plug it in directly.  Everything compiles, nothing
+works. There is shared library / weird linker dark voodoo crap going on.
+
+*I* am not a powerful enough Jedi to get around it, and I think it has
+more to do with the Dark Side of the Force than with the Light Side. So
+gawkfts.h now has:
+
+       #define fts_children gawk_fts_children
+       #define fts_close gawk_fts_close
+       #define fts_open gawk_fts_open
+       #define fts_read gawk_fts_read
+       #define fts_set gawk_fts_set
+
+to rename things, and it all compiles and works.
+
+The Plan 9 guys got it right when they threw eveything out and started
+over again. I sure wish I could.
+
+On the bright side, this is stuff that would have had to be done anyway
+for at least MinGW, and maybe for some other systems. So it's not wasted
+effort. But it sure is sad that it's necessary.
+
+Arnold Robbins
address@hidden
diff --git a/extension/configh.in b/extension/configh.in
index e621a13..ee00ae6 100644
--- a/extension/configh.in
+++ b/extension/configh.in
@@ -31,15 +31,6 @@
 /* Define to 1 if you have the <fnmatch.h> header file. */
 #undef HAVE_FNMATCH_H
 
-/* Define to 1 if you have the <fts.h> header file. */
-#undef HAVE_FTS_H
-
-/* Define to 1 if you have the `fts_open' function. */
-#undef HAVE_FTS_OPEN
-
-/* Define to 1 if you have the `fts_read' function. */
-#undef HAVE_FTS_READ
-
 /* Define to 1 if you have the `getdtablesize' function. */
 #undef HAVE_GETDTABLESIZE
 
diff --git a/extension/configure b/extension/configure
index 632c0d7..68d0ebc 100755
--- a/extension/configure
+++ b/extension/configure
@@ -13715,7 +13715,7 @@ then
        CFLAGS="$CFLAGS -Wall -Wextra"
 fi
 
-for ac_header in dirent.h fnmatch.h fts.h time.h sys/time.h sys/select.h
+for ac_header in dirent.h fnmatch.h time.h sys/time.h sys/select.h
 do :
   as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
 ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" 
"$ac_includes_default"
@@ -13729,7 +13729,7 @@ fi
 done
 
 
-for ac_func in fdopendir fnmatch fts_open fts_read gettimeofday \
+for ac_func in fdopendir fnmatch gettimeofday \
                getdtablesize nanosleep select GetSystemTimeAsFileTime
 do :
   as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
diff --git a/extension/configure.ac b/extension/configure.ac
index 9a3ddd9..d0e45c9 100644
--- a/extension/configure.ac
+++ b/extension/configure.ac
@@ -49,9 +49,9 @@ then
        CFLAGS="$CFLAGS -Wall -Wextra"
 fi
 
-AC_CHECK_HEADERS(dirent.h fnmatch.h fts.h time.h sys/time.h sys/select.h)
+AC_CHECK_HEADERS(dirent.h fnmatch.h time.h sys/time.h sys/select.h)
 
-AC_CHECK_FUNCS(fdopendir fnmatch fts_open fts_read gettimeofday \
+AC_CHECK_FUNCS(fdopendir fnmatch gettimeofday \
                getdtablesize nanosleep select GetSystemTimeAsFileTime)
 
 dnl checks for compiler characteristics
diff --git a/extension/filefuncs.c b/extension/filefuncs.c
index 89eaffc..bc2f2af 100644
--- a/extension/filefuncs.c
+++ b/extension/filefuncs.c
@@ -50,15 +50,8 @@
 #define _(msgid)  gettext(msgid)
 #define N_(msgid) msgid
 
-#if defined(HAVE_FTS_H) && defined(HAVE_FTS_OPEN) && defined(HAVE_FTS_READ)
-#define HAVE_FTS_ROUTINES
-#endif
-
-
-#ifdef HAVE_FTS_ROUTINES
-#include <fts.h>
+#include "gawkfts.h"
 #include "stack.h"
-#endif
 
 static const gawk_api_t *api;  /* for convenience macros to work */
 static awk_ext_id_t *ext_id;
@@ -384,12 +377,10 @@ static awk_bool_t
 init_filefuncs(void)
 {
        int errors = 0;
-
-       /* at least right now, only FTS needs initializing */
-#ifdef HAVE_FTS_ROUTINES
        int i;
        awk_value_t value;
 
+       /* at least right now, only FTS needs initializing */
        static struct flagtab {
                const char *name;
                int value;
@@ -412,11 +403,9 @@ init_filefuncs(void)
                        errors++;
                }
        }
-#endif
        return errors == 0;
 }
 
-#ifdef HAVE_FTS_ROUTINES
 static int fts_errors = 0;
 
 /* fill_stat_element --- fill in stat element of array */
@@ -613,7 +602,6 @@ process(FTS *heirarchy, awk_array_t destarray, int seedot)
                }
        }
 }
-#endif
 
 /*  do_fts --- walk a heirarchy and fill in an array */
 
@@ -626,7 +614,6 @@ process(FTS *heirarchy, awk_array_t destarray, int seedot)
 static awk_value_t *
 do_fts(int nargs, awk_value_t *result)
 {
-#ifdef HAVE_FTS_ROUTINES
        awk_value_t pathlist, flagval, dest;
        awk_flat_array_t *path_array = NULL;
        char **pathvector = NULL;
@@ -720,10 +707,6 @@ out:
                (void) release_flattened_array(pathlist.array_cookie, 
path_array);
 
        return make_number(ret, result);
-#else
-       update_ERRNO_int(EINVAL);
-       return make_number(-1, result);
-#endif
 }
 
 static awk_ext_func_t func_table[] = {
diff --git a/extension/fts.3 b/extension/fts.3
new file mode 100644
index 0000000..9131d04
--- /dev/null
+++ b/extension/fts.3
@@ -0,0 +1,773 @@
+.\"    $NetBSD: fts.3,v 1.30 2011/03/30 16:29:26 jruoho Exp $
+.\"
+.\" Copyright (c) 1989, 1991, 1993, 1994
+.\"    The Regents of the University of California.  All rights reserved.
+.\"
+.\" 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. 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.
+.\"
+.\"     @(#)fts.3      8.5 (Berkeley) 4/16/94
+.\"
+.Dd March 30, 2011
+.Dt FTS 3
+.Os
+.Sh NAME
+.Nm fts ,
+.Nm fts_open ,
+.Nm fts_read ,
+.Nm fts_children ,
+.Nm fts_set ,
+.Nm fts_close
+.Nd traverse a file hierarchy
+.Sh LIBRARY
+.Lb libc
+.Sh SYNOPSIS
+.In sys/types.h
+.In sys/stat.h
+.In fts.h
+.Ft FTS *
+.Fo fts_open
+.Fa "char * const *path_argv"
+.Fa "int options"
+.Fa "int (*compar)(const FTSENT **, const FTSENT **)"
+.Fc
+.Ft FTSENT *
+.Fn fts_read "FTS *ftsp"
+.Ft FTSENT *
+.Fn fts_children "FTS *ftsp" "int options"
+.Ft int
+.Fn fts_set "FTS *ftsp" "FTSENT *f" "int options"
+.Ft int
+.Fn fts_close "FTS *ftsp"
+.Sh DESCRIPTION
+The
+.Nm
+functions are provided for traversing
+.Ux
+file hierarchies.
+A simple overview is that the
+.Fn fts_open
+function returns a
+.Dq handle
+on a file hierarchy, which is then supplied to
+the other
+.Nm
+functions.
+The function
+.Fn fts_read
+returns a pointer to a structure describing one of the files in the file
+hierarchy.
+The function
+.Fn fts_children
+returns a pointer to a linked list of structures, each of which describes
+one of the files contained in a directory in the hierarchy.
+In general, directories are visited two distinguishable times; in pre-order
+(before any of their descendants are visited) and in post-order (after all
+of their descendants have been visited).
+Files are visited once.
+It is possible to walk the hierarchy
+.Dq logically
+(ignoring symbolic links)
+or physically (visiting symbolic links), order the walk of the hierarchy or
+prune and/or re-visit portions of the hierarchy.
+.Pp
+Two structures are defined (and typedef'd) in the include file
+.In fts.h .
+The first is
+.Fa FTS ,
+the structure that represents the file hierarchy itself.
+The second is
+.Fa FTSENT ,
+the structure that represents a file in the file
+hierarchy.
+Normally, an
+.Fa FTSENT
+structure is returned for every file in the file
+hierarchy.
+In this manual page,
+.Dq file
+and
+.Dq Fa FTSENT No structure
+are generally
+interchangeable.
+The
+.Fa FTSENT
+structure contains at least the following fields, which are
+described in greater detail below:
+.Bd -literal -offset 2n
+typedef struct _ftsent {
+       u_short fts_info;               /* flags for FTSENT structure */
+       char *fts_accpath;              /* access path */
+       char *fts_path;                 /* root path */
+       short fts_pathlen;              /* strlen(fts_path) */
+       char *fts_name;                 /* file name */
+       short fts_namelen;              /* strlen(fts_name) */
+       short fts_level;                /* depth (\-1 to N) */
+       int fts_errno;                  /* file errno */
+       long fts_number;                /* local numeric value */
+       void *fts_pointer;              /* local address value */
+       struct ftsent *fts_parent;      /* parent directory */
+       struct ftsent *fts_link;        /* next file structure */
+       struct ftsent *fts_cycle;       /* cycle structure */
+       struct stat *fts_statp;         /* stat(2) information */
+} FTSENT;
+.Ed
+.Pp
+These fields are defined as follows:
+.Bl -tag -width "fts_namelen"
+.It Fa fts_info
+One of the following flags describing the returned
+.Fa FTSENT
+structure and
+the file it represents.
+With the exception of directories without errors
+.Pq Dv FTS_D ,
+all of these
+entries are terminal, that is, they will not be revisited, nor will any
+of their descendants be visited.
+.Bl  -tag -width FTS_DEFAULT
+.It Dv FTS_D
+A directory being visited in pre-order.
+.It Dv FTS_DC
+A directory that causes a cycle in the tree.
+(The
+.Fa fts_cycle
+field of the
+.Fa FTSENT
+structure will be filled in as well).
+.It Dv FTS_DEFAULT
+Any
+.Fa FTSENT
+structure that represents a file type not explicitly described
+by one of the other
+.Fa fts_info
+values.
+.It Dv FTS_DNR
+A directory which cannot be read.
+This is an error return, and the
+.Fa fts_errno
+field will be set to indicate what caused the error.
+.It Dv FTS_DOT
+A file named
+.Ql \&.
+or
+.Ql ..
+which was not specified as a file name to
+.Fn fts_open
+(see
+.Dv FTS_SEEDOT ) .
+.It Dv FTS_DP
+A directory being visited in post-order.
+The contents of the
+.Fa FTSENT
+structure will be unchanged from when
+it was returned in pre-order, i.e., with the
+.Fa fts_info
+field set to
+.Dv FTS_D .
+.It Dv FTS_ERR
+This is an error return, and the
+.Fa fts_errno
+field will be set to indicate what caused the error.
+.It Dv FTS_F
+A regular file.
+.It Dv FTS_NS
+A file for which no
+.Xr stat 2
+information was available.
+The contents of the
+.Fa fts_statp
+field are undefined.
+This is an error return, and the
+.Fa fts_errno
+field will be set to indicate what caused the error.
+.It Dv FTS_NSOK
+A file for which no
+.Xr stat 2
+information was requested.
+The contents of the
+.Fa fts_statp
+field are undefined.
+.It Dv FTS_SL
+A symbolic link.
+.It Dv FTS_SLNONE
+A symbolic link with a non-existent target.
+The contents of the
+.Fa fts_statp
+field reference the file characteristic information for the symbolic link
+itself.
+.It Dv FTS_W
+A whiteout object.
+.El
+.It Fa fts_accpath
+A path for accessing the file from the current directory.
+.It Fa fts_path
+The path for the file relative to the root of the traversal.
+This path contains the path specified to
+.Fn fts_open
+as a prefix.
+.It Fa fts_pathlen
+The length of the string referenced by
+.Fa fts_path .
+.It Fa fts_name
+The name of the file.
+.It Fa fts_namelen
+The length of the string referenced by
+.Fa fts_name .
+.It Fa fts_level
+The depth of the traversal, numbered from \-1 to N, where this file
+was found.
+The
+.Fa FTSENT
+structure representing the parent of the starting point (or root)
+of the traversal is numbered \-1, and the
+.Fa FTSENT
+structure for the root
+itself is numbered 0.
+.It Fa fts_errno
+Upon return of a
+.Fa FTSENT
+structure from the
+.Fn fts_children
+or
+.Fn fts_read
+functions, with its
+.Fa fts_info
+field set to
+.Dv FTS_DNR ,
+.Dv FTS_ERR
+or
+.Dv FTS_NS ,
+the
+.Fa fts_errno
+field contains the value of the external variable
+.Va errno
+specifying the cause of the error.
+Otherwise, the contents of the
+.Fa fts_errno
+field are undefined.
+.It Fa fts_number
+This field is provided for the use of the application program and is
+not modified by the
+.Nm
+functions.
+It is initialized to 0.
+.It Fa fts_pointer
+This field is provided for the use of the application program and is
+not modified by the
+.Nm
+functions.
+It is initialized to
+.Dv NULL .
+.It Fa fts_parent
+A pointer to the
+.Fa FTSENT
+structure referencing the file in the hierarchy
+immediately above the current file, i.e., the directory of which this
+file is a member.
+A parent structure for the initial entry point is provided as well,
+however, only the
+.Fa fts_level ,
+.Fa fts_number
+and
+.Fa fts_pointer
+fields are guaranteed to be initialized.
+.It Fa fts_link
+Upon return from the
+.Fn fts_children
+function, the
+.Fa fts_link
+field points to the next structure in the
+.Dv NULL Ns -terminated
+linked list of directory members.
+Otherwise, the contents of the
+.Fa fts_link
+field are undefined.
+.It Fa fts_cycle
+If a directory causes a cycle in the hierarchy (see
+.Dv FTS_DC ) ,
+either because
+of a hard link between two directories, or a symbolic link pointing to a
+directory, the
+.Fa fts_cycle
+field of the structure will point to the
+.Fa FTSENT
+structure in the hierarchy that references the same file as the current
+.Fa FTSENT
+structure.
+Otherwise, the contents of the
+.Fa fts_cycle
+field are undefined.
+.It Fa fts_statp
+A pointer to
+.Xr stat 2
+information for the file.
+.El
+.Pp
+A single buffer is used for all of the paths of all of the files in the
+file hierarchy.
+Therefore, the
+.Fa fts_path
+and
+.Fa fts_accpath
+fields are guaranteed to be
+.Dv NULL Ns -terminated
+.Em only
+for the file most recently returned by
+.Fn fts_read .
+To use these fields to reference any files represented by other
+.Fa FTSENT
+structures will require that the path buffer be modified using the
+information contained in that
+.Fa FTSENT
+structure's
+.Fa fts_pathlen
+field.
+Any such modifications should be undone before further calls to
+.Fn fts_read
+are attempted.
+The
+.Fa fts_name
+field is always
+.Dv NULL Ns -terminated .
+.Sh FTS_OPEN
+The
+.Fn fts_open
+function takes a pointer to an array of character pointers naming one
+or more paths which make up a logical file hierarchy to be traversed.
+The array must be terminated by a
+.Dv NULL
+pointer.
+.Pp
+There are
+a number of options, at least one of which (either
+.Dv FTS_LOGICAL
+or
+.Dv FTS_PHYSICAL )
+must be specified.
+The options are selected by
+.Em or Ns 'ing
+the following values:
+.Bl -tag -width "FTS_COMFOLLOW "
+.It Dv FTS_COMFOLLOW
+This option causes any symbolic link specified as a root path to be
+followed immediately whether or not
+.Dv FTS_LOGICAL
+is also specified.
+.It Dv FTS_LOGICAL
+This option causes the
+.Nm
+routines to return
+.Fa FTSENT
+structures for the targets of symbolic links
+instead of the symbolic links themselves.
+If this option is set, the only symbolic links for which
+.Fa FTSENT
+structures
+are returned to the application are those referencing non-existent files.
+Either
+.Dv FTS_LOGICAL
+or
+.Dv FTS_PHYSICAL
+.Em must
+be provided to the
+.Fn fts_open
+function.
+.It Dv FTS_NOCHDIR
+As a performance optimization, the
+.Nm
+functions change directories as they walk the file hierarchy.
+This has the side-effect that an application cannot rely on being
+in any particular directory during the traversal.
+The
+.Dv FTS_NOCHDIR
+option turns off this optimization, and the
+.Nm
+functions will not change the current directory.
+Note that applications should not themselves change their current directory
+and try to access files unless
+.Dv FTS_NOCHDIR
+is specified and absolute
+pathnames were provided as arguments to
+.Fn fts_open .
+.It Dv FTS_NOSTAT
+By default, returned
+.Fa FTSENT
+structures reference file characteristic information (the
+.Fa statp
+field) for each file visited.
+This option relaxes that requirement as a performance optimization,
+allowing the
+.Nm
+functions to set the
+.Fa fts_info
+field to
+.Dv FTS_NSOK
+and leave the contents of the
+.Fa statp
+field undefined.
+.It Dv FTS_PHYSICAL
+This option causes the
+.Nm
+routines to return
+.Fa FTSENT
+structures for symbolic links themselves instead
+of the target files they point to.
+If this option is set,
+.Fa FTSENT
+structures for all symbolic links in the
+hierarchy are returned to the application.
+Either
+.Dv FTS_LOGICAL
+or
+.Dv FTS_PHYSICAL
+.Em must
+be provided to the
+.Fn fts_open
+function.
+.It Dv FTS_SEEDOT
+By default, unless they are specified as path arguments to
+.Fn fts_open ,
+any files named
+.Ql \&.
+or
+.Ql ..
+encountered in the file hierarchy are ignored.
+This option causes the
+.Nm
+routines to return
+.Fa FTSENT
+structures for them.
+.It Dv FTS_WHITEOUT
+Return whiteout entries, which are normally hidden.
+.It Dv FTS_XDEV
+This option prevents
+.Nm
+from descending into directories that have a different device number
+than the file from which the descent began.
+.El
+.Pp
+The argument
+.Fn compar
+specifies a user-defined function which may be used to order the traversal
+of the hierarchy.
+It
+takes two pointers to pointers to
+.Fa FTSENT
+structures as arguments and
+should return a negative value, zero, or a positive value to indicate
+if the file referenced by its first argument comes before, in any order
+with respect to, or after, the file referenced by its second argument.
+The
+.Fa fts_accpath ,
+.Fa fts_path
+and
+.Fa fts_pathlen
+fields of the
+.Fa FTSENT
+structures may
+.Em never
+be used in this comparison.
+If the
+.Fa fts_info
+field is set to
+.Dv FTS_NS
+or
+.Dv FTS_NSOK ,
+the
+.Fa fts_statp
+field may not either.
+If the
+.Fn compar
+argument is
+.Dv NULL ,
+the directory traversal order is in the order listed in
+.Fa path_argv
+for the root paths, and in the order listed in the directory for
+everything else.
+.Sh FTS_READ
+The
+.Fn fts_read
+function returns a pointer to an
+.Fa FTSENT
+structure describing a file in
+the hierarchy.
+Directories (that are readable and do not cause cycles) are visited at
+least twice, once in pre-order and once in post-order.
+All other files are visited at least once.
+(Hard links between directories that do not cause cycles or symbolic
+links to symbolic links may cause files to be visited more than once,
+or directories more than twice.)
+.Pp
+If all the members of the hierarchy have been returned,
+.Fn fts_read
+returns
+.Dv NULL
+and sets the external variable
+.Va errno
+to 0.
+If an error unrelated to a file in the hierarchy occurs,
+.Fn fts_read
+returns
+.Dv NULL
+and sets
+.Va errno
+appropriately.
+If an error related to a returned file occurs, a pointer to an
+.Fa FTSENT
+structure is returned, and
+.Va errno
+may or may not have been set (see
+.Fa fts_info ) .
+.Pp
+The
+.Fa FTSENT
+structures returned by
+.Fn fts_read
+may be overwritten after a call to
+.Fn fts_close
+on the same file hierarchy stream, or, after a call to
+.Fn fts_read
+on the same file hierarchy stream unless they represent a file of type
+directory, in which case they will not be overwritten until after a call to
+.Fn fts_read
+after the
+.Fa FTSENT
+structure has been returned by the function
+.Fn fts_read
+in post-order.
+.Sh FTS_CHILDREN
+The
+.Fn fts_children
+function returns a pointer to an
+.Fa FTSENT
+structure describing the first entry in a
+.Dv NULL Ns -terminated
+linked list of the files in the directory represented by the
+.Fa FTSENT
+structure most recently returned by
+.Fn fts_read .
+The list is linked through the
+.Fa fts_link
+field of the
+.Fa FTSENT
+structure, and is ordered by the user-specified comparison function, if any.
+Repeated calls to
+.Fn fts_children
+will recreate this linked list.
+.Pp
+As a special case, if
+.Fn fts_read
+has not yet been called for a hierarchy,
+.Fn fts_children
+will return a pointer to the files in the logical directory specified to
+.Fn fts_open ,
+i.e., the arguments specified to
+.Fn fts_open .
+Otherwise, if the
+.Fa FTSENT
+structure most recently returned by
+.Fn fts_read
+is not a directory being visited in pre-order,
+or the directory does not contain any files,
+.Fn fts_children
+returns
+.Dv NULL
+and sets
+.Va errno
+to zero.
+If an error occurs,
+.Fn fts_children
+returns
+.Dv NULL
+and sets
+.Va errno
+appropriately.
+.Pp
+The
+.Fa FTSENT
+structures returned by
+.Fn fts_children
+may be overwritten after a call to
+.Fn fts_children ,
+.Fn fts_close
+or
+.Fn fts_read
+on the same file hierarchy stream.
+.Pp
+.Em Option
+may be set to the following value:
+.Bl -tag -width "FTS_COMFOLLOW "
+.It Dv FTS_NAMEONLY
+Only the names of the files are needed.
+The contents of all the fields in the returned linked list of structures
+are undefined with the exception of the
+.Fa fts_name
+and
+.Fa fts_namelen
+fields.
+.El
+.Sh FTS_SET
+The function
+.Fn fts_set
+allows the user application to determine further processing for the
+file
+.Fa f
+of the stream
+.Fa ftsp .
+The
+.Fn fts_set
+function
+returns 0 on success, and \-1 if an error occurs.
+.Em Option
+must be set to one of the following values:
+.Bl -tag -width "FTS_COMFOLLOW "
+.It Dv FTS_AGAIN
+Re-visit the file; any file type may be re-visited.
+The next call to
+.Fn fts_read
+will return the referenced file.
+The
+.Fa fts_stat
+and
+.Fa fts_info
+fields of the structure will be reinitialized at that time,
+but no other fields will have been changed.
+This option is meaningful only for the most recently returned
+file from
+.Fn fts_read .
+Normal use is for post-order directory visits, where it causes the
+directory to be re-visited (in both pre and post-order) as well as all
+of its descendants.
+.It Dv FTS_FOLLOW
+The referenced file must be a symbolic link.
+If the referenced file is the one most recently returned by
+.Fn fts_read ,
+the next call to
+.Fn fts_read
+returns the file with the
+.Fa fts_info
+and
+.Fa fts_statp
+fields reinitialized to reflect the target of the symbolic link instead
+of the symbolic link itself.
+If the file is one of those most recently returned by
+.Fn fts_children ,
+the
+.Fa fts_info
+and
+.Fa fts_statp
+fields of the structure, when returned by
+.Fn fts_read ,
+will reflect the target of the symbolic link instead of the symbolic link
+itself.
+In either case, if the target of the symbolic link does not exist the
+fields of the returned structure will be unchanged and the
+.Fa fts_info
+field will be set to
+.Dv FTS_SLNONE .
+.Pp
+If the target of the link is a directory, the pre-order return, followed
+by the return of all of its descendants, followed by a post-order return,
+is done.
+.It Dv FTS_SKIP
+No descendants of this file are visited.
+The file may be one of those most recently returned by either
+.Fn fts_children
+or
+.Fn fts_read .
+.El
+.Sh FTS_CLOSE
+The
+.Fn fts_close
+function closes a file hierarchy stream
+.Fa ftsp
+and restores the current directory to the directory from which
+.Fn fts_open
+was called to open
+.Fa ftsp .
+The
+.Fn fts_close
+function
+returns 0 on success, and \-1 if an error occurs.
+.Sh ERRORS
+The function
+.Fn fts_open
+may fail and set
+.Va errno
+for any of the errors specified for the library functions
+.Xr open 2
+and
+.Xr malloc 3 .
+.Pp
+The function
+.Fn fts_close
+may fail and set
+.Va errno
+for any of the errors specified for the library functions
+.Xr chdir 2
+and
+.Xr close 2 .
+.Pp
+The functions
+.Fn fts_read
+and
+.Fn fts_children
+may fail and set
+.Va errno
+for any of the errors specified for the library functions
+.Xr chdir 2 ,
+.Xr malloc 3 ,
+.Xr opendir 3 ,
+.Xr readdir 3
+and
+.Xr stat 2 .
+.Pp
+In addition,
+.Fn fts_children ,
+.Fn fts_open
+and
+.Fn fts_set
+may fail and set
+.Va errno
+as follows:
+.Bl -tag -width Er
+.It Bq Er EINVAL
+The options were invalid.
+.El
+.Sh SEE ALSO
+.Xr find 1 ,
+.Xr chdir 2 ,
+.Xr stat 2 ,
+.Xr qsort 3 ,
+.Xr symlink 7
+.Sh STANDARDS
+The
+.Nm
+utility was expected to be included in the
+.St -p1003.1-88
+revision.
+But twenty years later, it still was not included in the
+.St -p1003.1-2008
+revision.
diff --git a/extension/gawkfts.c b/extension/gawkfts.c
new file mode 100644
index 0000000..f3a3cc0
--- /dev/null
+++ b/extension/gawkfts.c
@@ -0,0 +1,1235 @@
+/*     $NetBSD: fts.c,v 1.44 2012/03/14 00:25:19 christos Exp $        */
+
+/*-
+ * Copyright (c) 1990, 1993, 1994
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * 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. 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 0
+static char sccsid[] = "@(#)fts.c      8.6 (Berkeley) 8/14/94";
+#else
+/* __RCSID("$NetBSD: fts.c,v 1.44 2012/03/14 00:25:19 christos Exp $"); */
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/* #include "namespace.h" */
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include "gawkfts.h"
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+#if ! HAVE_NBTOOL_CONFIG_H
+#define        HAVE_STRUCT_DIRENT_D_NAMLEN
+#endif
+*/
+
+static FTSENT  *fts_alloc(FTS *, const char *, size_t);
+static FTSENT  *fts_build(FTS *, int);
+static void     fts_free(FTSENT *);
+static void     fts_lfree(FTSENT *);
+static void     fts_load(FTS *, FTSENT *);
+static size_t   fts_maxarglen(char * const *);
+static size_t   fts_pow2(size_t);
+static int      fts_palloc(FTS *, size_t);
+static void     fts_padjust(FTS *, FTSENT *);
+static FTSENT  *fts_sort(FTS *, FTSENT *, size_t);
+static unsigned short fts_stat(FTS *, FTSENT *, int);
+static int      fts_safe_changedir(const FTS *, const FTSENT *, int,
+    const char *);
+
+#if defined(ALIGNBYTES) && defined(ALIGN)
+#define        FTS_ALLOC_ALIGNED       1
+#else
+#undef FTS_ALLOC_ALIGNED
+#endif
+
+#ifndef ftsent_namelen_truncate
+#define ftsent_namelen_truncate(a)     \
+    ((a) > UINT_MAX ? UINT_MAX : (unsigned int)(a))
+#endif
+#ifndef ftsent_pathlen_truncate
+#define ftsent_pathlen_truncate(a) \
+    ((a) > UINT_MAX ? UINT_MAX : (unsigned int)(a))
+#endif
+#ifndef fts_pathlen_truncate
+#define fts_pathlen_truncate(a)        \
+    ((a) > UINT_MAX ? UINT_MAX : (unsigned int)(a))
+#endif
+#ifndef fts_nitems_truncate
+#define fts_nitems_truncate(a) \
+    ((a) > UINT_MAX ? UINT_MAX : (unsigned int)(a))
+#endif
+
+#define        ISDOT(a)        (a[0] == '.' && (!a[1] || (a[1] == '.' && 
!a[2])))
+
+#define        CLR(opt)        (sp->fts_options &= ~(opt))
+#define        ISSET(opt)      (sp->fts_options & (opt))
+#define        SET(opt)        (sp->fts_options |= (opt))
+
+#define        CHDIR(sp, path) (!ISSET(FTS_NOCHDIR) && chdir(path))
+#define        FCHDIR(sp, fd)  (!ISSET(FTS_NOCHDIR) && fchdir(fd))
+
+/* fts_build flags */
+#define        BCHILD          1               /* fts_children */
+#define        BNAMES          2               /* fts_children, names only */
+#define        BREAD           3               /* fts_read */
+
+#ifndef DTF_HIDEW
+#undef FTS_WHITEOUT
+#endif
+
+#define _DIAGASSERT(expression)
+
+FTS *
+fts_open(char * const *argv, int options,
+    int (*compar)(const FTSENT **, const FTSENT **))
+{
+       FTS *sp;
+       FTSENT *p, *root;
+       size_t nitems;
+       FTSENT *parent, *tmp = NULL;    /* pacify gcc */
+       size_t len;
+
+       _DIAGASSERT(argv != NULL);
+
+       /* Options check. */
+       if (options & ~FTS_OPTIONMASK) {
+               errno = EINVAL;
+               return (NULL);
+       }
+
+       /* Allocate/initialize the stream */
+       if ((sp = malloc((unsigned int)sizeof(FTS))) == NULL)
+               return (NULL);
+       memset(sp, 0, sizeof(FTS));
+       sp->fts_compar = compar;
+       sp->fts_options = options;
+
+       /* Logical walks turn on NOCHDIR; symbolic links are too hard. */
+       if (ISSET(FTS_LOGICAL))
+               SET(FTS_NOCHDIR);
+
+       /*
+        * Start out with 1K of path space, and enough, in any case,
+        * to hold the user's paths.
+        */
+       if (fts_palloc(sp, MAX(fts_maxarglen(argv), MAXPATHLEN)))
+               goto mem1;
+
+       /* Allocate/initialize root's parent. */
+       if ((parent = fts_alloc(sp, "", 0)) == NULL)
+               goto mem2;
+       parent->fts_level = FTS_ROOTPARENTLEVEL;
+
+       /* Allocate/initialize root(s). */
+       for (root = NULL, nitems = 0; *argv; ++argv, ++nitems) {
+               /* Don't allow zero-length paths. */
+               if ((len = strlen(*argv)) == 0) {
+                       errno = ENOENT;
+                       goto mem3;
+               }
+
+               if ((p = fts_alloc(sp, *argv, len)) == NULL)
+                       goto mem3;
+               p->fts_level = FTS_ROOTLEVEL;
+               p->fts_parent = parent;
+               p->fts_accpath = p->fts_name;
+               p->fts_info = fts_stat(sp, p, ISSET(FTS_COMFOLLOW));
+
+               /* Command-line "." and ".." are real directories. */
+               if (p->fts_info == FTS_DOT)
+                       p->fts_info = FTS_D;
+
+               /*
+                * If comparison routine supplied, traverse in sorted
+                * order; otherwise traverse in the order specified.
+                */
+               if (compar) {
+                       p->fts_link = root;
+                       root = p;
+               } else {
+                       p->fts_link = NULL;
+                       if (root == NULL)
+                               tmp = root = p;
+                       else {
+                               tmp->fts_link = p;
+                               tmp = p;
+                       }
+               }
+       }
+       if (compar && nitems > 1)
+               root = fts_sort(sp, root, nitems);
+
+       /*
+        * 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_INIT
+        * so that everything about the "current" node is ignored.
+        */
+       if ((sp->fts_cur = fts_alloc(sp, "", 0)) == NULL)
+               goto mem3;
+       sp->fts_cur->fts_link = root;
+       sp->fts_cur->fts_info = FTS_INIT;
+
+       /*
+        * 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.
+        */
+#ifndef O_CLOEXEC
+#define O_CLOEXEC 0
+#endif
+       if (!ISSET(FTS_NOCHDIR)) {
+               if ((sp->fts_rfd = open(".", O_RDONLY | O_CLOEXEC, 0)) == -1)
+                       SET(FTS_NOCHDIR);
+       }
+
+       if (nitems == 0)
+               fts_free(parent);
+
+       return (sp);
+
+mem3:  fts_lfree(root);
+       fts_free(parent);
+mem2:  free(sp->fts_path);
+mem1:  free(sp);
+       return (NULL);
+}
+
+static void
+fts_load(FTS *sp, FTSENT *p)
+{
+       size_t len;
+       char *cp;
+
+       _DIAGASSERT(sp != NULL);
+       _DIAGASSERT(p != NULL);
+
+       /*
+        * 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.  From fts_open it's
+        * known that the path will fit.
+        */
+       len = p->fts_pathlen = p->fts_namelen;
+       memmove(sp->fts_path, p->fts_name, len + 1);
+       if ((cp = strrchr(p->fts_name, '/')) && (cp != p->fts_name || cp[1])) {
+               len = strlen(++cp);
+               memmove(p->fts_name, cp, len + 1);
+               p->fts_namelen = ftsent_namelen_truncate(len);
+       }
+       p->fts_accpath = p->fts_path = sp->fts_path;
+       sp->fts_dev = p->fts_dev;
+}
+
+int
+fts_close(FTS *sp)
+{
+       FTSENT *freep, *p;
+       int saved_errno = 0;
+
+       _DIAGASSERT(sp != NULL);
+
+       /*
+        * 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.
+        */
+       if (sp->fts_cur) {
+               if (sp->fts_cur->fts_flags & FTS_SYMFOLLOW)
+                       (void)close(sp->fts_cur->fts_symfd);
+               for (p = sp->fts_cur; p->fts_level >= FTS_ROOTLEVEL;) {
+                       freep = p;
+                       p = p->fts_link ? p->fts_link : p->fts_parent;
+                       fts_free(freep);
+               }
+               fts_free(p);
+       }
+
+       /* Free up child linked list, sort array, path buffer. */
+       if (sp->fts_child)
+               fts_lfree(sp->fts_child);
+       if (sp->fts_array)
+               free(sp->fts_array);
+       free(sp->fts_path);
+
+       /* Return to original directory, save errno if necessary. */
+       if (!ISSET(FTS_NOCHDIR)) {
+               if (fchdir(sp->fts_rfd) == -1)
+                       saved_errno = errno;
+               (void)close(sp->fts_rfd);
+       }
+
+       /* Free up the stream pointer. */
+       free(sp);
+       if (saved_errno) {
+               errno = saved_errno;
+               return -1;
+       }
+
+       return 0;
+}
+
+#if !defined(__FTS_COMPAT_TAILINGSLASH)
+
+/*
+ * Special case of "/" at the end of the path so that slashes aren't
+ * appended which would cause paths to be written as "....//foo".
+ */
+#define        NAPPEND(p)                                                      
\
+       (p->fts_path[p->fts_pathlen - 1] == '/'                         \
+           ? p->fts_pathlen - 1 : p->fts_pathlen)
+
+#else /* !defined(__FTS_COMPAT_TAILINGSLASH) */
+
+/*
+ * compatibility with the old behaviour.
+ *
+ * Special case a root of "/" so that slashes aren't appended which would
+ * cause 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)
+
+#endif /* !defined(__FTS_COMPAT_TAILINGSLASH) */
+
+FTSENT *
+fts_read(FTS *sp)
+{
+       FTSENT *p, *tmp;
+       int instr;
+       char *t;
+       int saved_errno;
+
+       _DIAGASSERT(sp != NULL);
+
+       /* If finished or unrecoverable error, return NULL. */
+       if (sp->fts_cur == NULL || ISSET(FTS_STOP))
+               return (NULL);
+
+       /* Set current node pointer. */
+       p = sp->fts_cur;
+
+       /* Save and zero out user instructions. */
+       instr = p->fts_instr;
+       p->fts_instr = FTS_NOINSTR;
+
+       /* Any type of file may be re-visited; re-stat and re-turn. */
+       if (instr == FTS_AGAIN) {
+               p->fts_info = fts_stat(sp, p, 0);
+               return (p);
+       }
+
+       /*
+        * Following a symlink -- SLNONE test allows application to see
+        * SLNONE and recover.  If indirecting through a symlink, have
+        * keep a pointer to current location.  If unable to get that
+        * pointer, follow fails.
+        */
+       if (instr == FTS_FOLLOW &&
+           (p->fts_info == FTS_SL || p->fts_info == FTS_SLNONE)) {
+               p->fts_info = fts_stat(sp, p, 1);
+               if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) {
+                       if ((p->fts_symfd = open(".", O_RDONLY | O_CLOEXEC, 0))
+                           == -1) {
+                               p->fts_errno = errno;
+                               p->fts_info = FTS_ERR;
+                       } else
+                               p->fts_flags |= FTS_SYMFOLLOW;
+               }
+               return (p);
+       }
+
+       /* Directory in pre-order. */
+       if (p->fts_info == FTS_D) {
+               /* If skipped or crossed mount point, do post-order visit. */
+               if (instr == FTS_SKIP ||
+                   (ISSET(FTS_XDEV) && p->fts_dev != sp->fts_dev)) {
+                       if (p->fts_flags & FTS_SYMFOLLOW)
+                               (void)close(p->fts_symfd);
+                       if (sp->fts_child) {
+                               fts_lfree(sp->fts_child);
+                               sp->fts_child = NULL;
+                       }
+                       p->fts_info = FTS_DP;
+                       return (p);
+               }
+
+               /* Rebuild if only read the names and now traversing. */
+               if (sp->fts_child && ISSET(FTS_NAMEONLY)) {
+                       CLR(FTS_NAMEONLY);
+                       fts_lfree(sp->fts_child);
+                       sp->fts_child = NULL;
+               }
+
+               /*
+                * Cd to the subdirectory.
+                *
+                * If have already read and now fail to chdir, whack the list
+                * to make the names come out right, and set the parent errno
+                * so the application will eventually get an error condition.
+                * Set the FTS_DONTCHDIR flag so that when we logically change
+                * directories back to the parent we don't do a chdir.
+                *
+                * If haven't read do so.  If the read fails, fts_build sets
+                * FTS_STOP or the fts_info field of the node.
+                */
+               if (sp->fts_child) {
+                       if (fts_safe_changedir(sp, p, -1, p->fts_accpath)) {
+                               p->fts_errno = errno;
+                               p->fts_flags |= FTS_DONTCHDIR;
+                               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)) == NULL) {
+                       if (ISSET(FTS_STOP))
+                               return (NULL);
+                       return (p);
+               }
+               p = sp->fts_child;
+               sp->fts_child = NULL;
+               goto name;
+       }
+
+       /* Move to the next node on this level. */
+next:  tmp = p;
+       if ((p = p->fts_link) != NULL) {
+               fts_free(tmp);
+
+               /*
+                * If reached the top, return to the original directory, and
+                * load the paths for the next root.
+                */
+               if (p->fts_level == FTS_ROOTLEVEL) {
+                       if (FCHDIR(sp, sp->fts_rfd)) {
+                               SET(FTS_STOP);
+                               return (NULL);
+                       }
+                       fts_load(sp, p);
+                       return (sp->fts_cur = p);
+               }
+
+               /*
+                * User may have called fts_set on the node.  If skipped,
+                * ignore.  If followed, get a file descriptor so we can
+                * get back if necessary.
+                */
+               if (p->fts_instr == FTS_SKIP)
+                       goto next;
+               if (p->fts_instr == FTS_FOLLOW) {
+                       p->fts_info = fts_stat(sp, p, 1);
+                       if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) {
+                               if ((p->fts_symfd =
+                                   open(".", O_RDONLY | O_CLOEXEC, 0)) == -1) {
+                                       p->fts_errno = errno;
+                                       p->fts_info = FTS_ERR;
+                               } else
+                                       p->fts_flags |= FTS_SYMFOLLOW;
+                       }
+                       p->fts_instr = FTS_NOINSTR;
+               }
+
+name:          t = sp->fts_path + NAPPEND(p->fts_parent);
+               *t++ = '/';
+               memmove(t, p->fts_name, (size_t)(p->fts_namelen + 1));
+               return (sp->fts_cur = p);
+       }
+
+       /* Move up to the parent node. */
+       p = tmp->fts_parent;
+       fts_free(tmp);
+
+       if (p->fts_level == FTS_ROOTPARENTLEVEL) {
+               /*
+                * Done; free everything up and set errno to 0 so the user
+                * can distinguish between error and EOF.
+                */
+               fts_free(p);
+               errno = 0;
+               return (sp->fts_cur = NULL);
+       }
+
+       /* Nul terminate the pathname. */
+       sp->fts_path[p->fts_pathlen] = '\0';
+
+       /*
+        * Return to the parent directory.  If at a root node or came through
+        * a symlink, go back through the file descriptor.  Otherwise, cd up
+        * one directory.
+        */
+       if (p->fts_level == FTS_ROOTLEVEL) {
+               if (FCHDIR(sp, sp->fts_rfd)) {
+                       SET(FTS_STOP);
+                       return (NULL);
+               }
+       } else if (p->fts_flags & FTS_SYMFOLLOW) {
+               if (FCHDIR(sp, p->fts_symfd)) {
+                       saved_errno = errno;
+                       (void)close(p->fts_symfd);
+                       errno = saved_errno;
+                       SET(FTS_STOP);
+                       return (NULL);
+               }
+               (void)close(p->fts_symfd);
+       } else if (!(p->fts_flags & FTS_DONTCHDIR) &&
+           fts_safe_changedir(sp, p->fts_parent, -1, "..")) {
+               SET(FTS_STOP);
+               return (NULL);
+       }
+       p->fts_info = p->fts_errno ? FTS_ERR : FTS_DP;
+       return (sp->fts_cur = p);
+}
+
+/*
+ * 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
+ * semantics to fts using fts_set.  An error return is allowed for similar
+ * reasons.
+ */
+/* ARGSUSED */
+int
+fts_set(FTS *sp, FTSENT *p, int instr)
+{
+
+       _DIAGASSERT(sp != NULL);
+       _DIAGASSERT(p != NULL);
+
+       if (instr && instr != FTS_AGAIN && instr != FTS_FOLLOW &&
+           instr != FTS_NOINSTR && instr != FTS_SKIP) {
+               errno = EINVAL;
+               return (1);
+       }
+       p->fts_instr = instr;
+       return (0);
+}
+
+FTSENT *
+fts_children(FTS *sp, int instr)
+{
+       FTSENT *p;
+       int fd;
+
+       _DIAGASSERT(sp != NULL);
+
+       if (instr && instr != FTS_NAMEONLY) {
+               errno = EINVAL;
+               return (NULL);
+       }
+
+       /* Set current node pointer. */
+       p = sp->fts_cur;
+
+       /*
+        * Errno set to 0 so user can distinguish empty directory from
+        * an error.
+        */
+       errno = 0;
+
+       /* Fatal errors stop here. */
+       if (ISSET(FTS_STOP))
+               return (NULL);
+
+       /* Return logical hierarchy of user's arguments. */
+       if (p->fts_info == FTS_INIT)
+               return (p->fts_link);
+
+       /*
+        * If not a directory being visited in pre-order, stop here.  Could
+        * allow FTS_DNR, assuming the user has fixed the problem, but the
+        * same effect is available with FTS_AGAIN.
+        */
+       if (p->fts_info != FTS_D /* && p->fts_info != FTS_DNR */)
+               return (NULL);
+
+       /* Free up any previous child list. */
+       if (sp->fts_child)
+               fts_lfree(sp->fts_child);
+
+       if (instr == FTS_NAMEONLY) {
+               SET(FTS_NAMEONLY);
+               instr = BNAMES;
+       } else
+               instr = BCHILD;
+
+       /*
+        * 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 != FTS_ROOTLEVEL || p->fts_accpath[0] == '/' ||
+           ISSET(FTS_NOCHDIR))
+               return (sp->fts_child = fts_build(sp, instr));
+
+       if ((fd = open(".", O_RDONLY, 0)) == -1)
+               return (sp->fts_child = NULL);
+       sp->fts_child = fts_build(sp, instr);
+       if (fchdir(fd)) {
+               (void)close(fd);
+               return (NULL);
+       }
+       (void)close(fd);
+       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 can do things quickly.  First, if it's a 4.4BSD file system, the type
+ * of the file is in the directory entry.  Otherwise, we assume that the number
+ * of subdirectories in a node is equal to the number of links to the parent.
+ * The former skips all stat calls.  The latter skips stat calls in any leaf
+ * directories and for any files after the subdirectories in the directory have
+ * been found, cutting the stat calls by about 2/3.
+ */
+static FTSENT *
+fts_build(FTS *sp, int type)
+{
+       struct dirent *dp;
+       FTSENT *p, *head;
+       size_t nitems;
+       FTSENT *cur, *tail;
+       DIR *dirp;
+       void *oldaddr;
+       size_t dnamlen;
+       int cderrno, descend, level, nlinks, saved_errno, nostat, doadjust;
+       size_t len, maxlen;
+#ifdef FTS_WHITEOUT
+       int oflag;
+#endif
+       char *cp = NULL;        /* pacify gcc */
+
+       _DIAGASSERT(sp != NULL);
+
+       /* 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 0 /* def FTS_WHITEOUT */
+       if (ISSET(FTS_WHITEOUT))
+               oflag = DTF_NODUP|DTF_REWIND;
+       else
+               oflag = DTF_HIDEW|DTF_NODUP|DTF_REWIND;
+#else
+#define        __opendir2(path, flag) opendir(path)
+#endif
+       if ((dirp = __opendir2(cur->fts_accpath, oflag)) == NULL) {
+               if (type == BREAD) {
+                       cur->fts_info = FTS_DNR;
+                       cur->fts_errno = errno;
+               }
+               return (NULL);
+       }
+
+       /*
+        * 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.
+        */
+       if (type == BNAMES) {
+               nlinks = 0;
+               nostat = 1;
+       } else if (ISSET(FTS_NOSTAT) && ISSET(FTS_PHYSICAL)) {
+               nlinks = cur->fts_nlink - (ISSET(FTS_SEEDOT) ? 0 : 2);
+               nostat = 1;
+       } else {
+               nlinks = -1;
+               nostat = 0;
+       }
+
+#ifdef notdef
+       (void)printf("nlinks == %d (cur: %d)\n", nlinks, cur->fts_nlink);
+       (void)printf("NOSTAT %d PHYSICAL %d SEEDOT %d\n",
+           ISSET(FTS_NOSTAT), ISSET(FTS_PHYSICAL), ISSET(FTS_SEEDOT));
+#endif
+       /*
+        * 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,
+        * but set a flag so we don't chdir after the post-order visit.
+        * 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.  There is a special case
+        * here, if there was nothing to stat then it's not an error to
+        * not be able to stat.  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.
+        */
+       cderrno = 0;
+       if (nlinks || type == BREAD) {
+               if (fts_safe_changedir(sp, cur, dirfd(dirp), NULL)) {
+                       if (nlinks && type == BREAD)
+                               cur->fts_errno = errno;
+                       cur->fts_flags |= FTS_DONTCHDIR;
+                       descend = 0;
+                       cderrno = errno;
+               } else
+                       descend = 1;
+       } else
+               descend = 0;
+
+       /*
+        * 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 can just append
+        * each new name into the path.
+        */
+       len = NAPPEND(cur);
+       if (ISSET(FTS_NOCHDIR)) {
+               cp = sp->fts_path + len;
+               *cp++ = '/';
+       }
+       len++;
+       maxlen = sp->fts_pathlen - len;
+
+#if defined(__FTS_COMPAT_LEVEL)
+       if (cur->fts_level == SHRT_MAX) {
+               (void)closedir(dirp);
+               cur->fts_info = FTS_ERR;
+               SET(FTS_STOP);
+               errno = ENAMETOOLONG;
+               return (NULL);
+       }
+#endif
+
+       level = cur->fts_level + 1;
+
+       /* Read the directory, attaching each entry to the `link' pointer. */
+       doadjust = 0;
+       for (head = tail = NULL, nitems = 0; (dp = readdir(dirp)) != NULL;) {
+
+               if (!ISSET(FTS_SEEDOT) && ISDOT(dp->d_name))
+                       continue;
+
+#if defined(HAVE_STRUCT_DIRENT_D_NAMLEN)
+               dnamlen = dp->d_namlen;
+#else
+               dnamlen = strlen(dp->d_name);
+#endif
+               if ((p = fts_alloc(sp, dp->d_name, dnamlen)) == NULL)
+                       goto mem1;
+               if (dnamlen >= maxlen) {        /* include space for NUL */
+                       oldaddr = sp->fts_path;
+                       if (fts_palloc(sp, dnamlen + len + 1)) {
+                               /*
+                                * 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)
+                                       fts_free(p);
+                               fts_lfree(head);
+                               (void)closedir(dirp);
+                               errno = saved_errno;
+                               cur->fts_info = FTS_ERR;
+                               SET(FTS_STOP);
+                               return (NULL);
+                       }
+                       /* Did realloc() change the pointer? */
+                       if (oldaddr != sp->fts_path) {
+                               doadjust = 1;
+                               if (ISSET(FTS_NOCHDIR))
+                                       cp = sp->fts_path + len;
+                       }
+                       maxlen = sp->fts_pathlen - len;
+               }
+
+#if defined(__FTS_COMPAT_LENGTH)
+               if (len + dnamlen >= USHRT_MAX) {
+                       /*
+                        * In an FTSENT, fts_pathlen is an unsigned short
+                        * so it is possible to wraparound here.
+                        * If we do, free up the current structure and the
+                        * structures already allocated, then error out
+                        * with ENAMETOOLONG.
+                        */
+                       fts_free(p);
+                       fts_lfree(head);
+                       (void)closedir(dirp);
+                       cur->fts_info = FTS_ERR;
+                       SET(FTS_STOP);
+                       errno = ENAMETOOLONG;
+                       return (NULL);
+               }
+#endif
+               p->fts_level = level;
+               p->fts_pathlen = ftsent_pathlen_truncate(len + dnamlen);
+               p->fts_parent = sp->fts_cur;
+
+#ifdef FTS_WHITEOUT
+               if (dp->d_type == DT_WHT)
+                       p->fts_flags |= FTS_ISW;
+#endif
+
+               if (cderrno) {
+                       if (nlinks) {
+                               p->fts_info = FTS_NS;
+                               p->fts_errno = cderrno;
+                       } else
+                               p->fts_info = FTS_NSOK;
+                       p->fts_accpath = cur->fts_accpath;
+               } else if (nlinks == 0
+#ifdef DT_DIR
+                   || (nostat &&
+                   dp->d_type != DT_DIR && dp->d_type != DT_UNKNOWN)
+#endif
+                   ) {
+                       p->fts_accpath =
+                           ISSET(FTS_NOCHDIR) ? p->fts_path : p->fts_name;
+                       p->fts_info = FTS_NSOK;
+               } else {
+                       /* Build a file name for fts_stat to stat. */
+                       if (ISSET(FTS_NOCHDIR)) {
+                               p->fts_accpath = p->fts_path;
+                               memmove(cp, p->fts_name,
+                                       (size_t)(p->fts_namelen + 1));
+                       } else
+                               p->fts_accpath = p->fts_name;
+                       /* Stat it. */
+                       p->fts_info = fts_stat(sp, p, 0);
+
+                       /* Decrement link count if applicable. */
+                       if (nlinks > 0 && (p->fts_info == FTS_D ||
+                           p->fts_info == FTS_DC || p->fts_info == FTS_DOT))
+                               --nlinks;
+               }
+
+               /* We walk in directory order so "ls -f" doesn't get upset. */
+               p->fts_link = NULL;
+               if (head == NULL)
+                       head = tail = p;
+               else {
+                       tail->fts_link = p;
+                       tail = p;
+               }
+               ++nitems;
+       }
+       (void)closedir(dirp);
+
+       /*
+        * If had to realloc the path, adjust the addresses for the rest
+        * of the tree.
+        */
+       if (doadjust)
+               fts_padjust(sp, head);
+
+       /*
+        * If not changing directories, reset the path back to original
+        * state.
+        */
+       if (ISSET(FTS_NOCHDIR)) {
+               if (len == sp->fts_pathlen || nitems == 0)
+                       --cp;
+               *cp = '\0';
+       }
+
+       /*
+        * If descended after called from fts_children or after called from
+        * fts_read and nothing found, get back.  At the root level we use
+        * the saved fd; if one of fts_open()'s arguments is a relative path
+        * to an empty directory, we wind up here with no other way back.  If
+        * can't get back, we're done.
+        */
+       if (descend && (type == BCHILD || !nitems) &&
+           (cur->fts_level == FTS_ROOTLEVEL ?
+           FCHDIR(sp, sp->fts_rfd) :
+           fts_safe_changedir(sp, cur->fts_parent, -1, ".."))) {
+               cur->fts_info = FTS_ERR;
+               SET(FTS_STOP);
+               return (NULL);
+       }
+
+       /* If didn't find anything, return NULL. */
+       if (!nitems) {
+               if (type == BREAD)
+                       cur->fts_info = FTS_DP;
+               return (NULL);
+       }
+
+       /* Sort the entries. */
+       if (sp->fts_compar && nitems > 1)
+               head = fts_sort(sp, head, nitems);
+       return (head);
+}
+
+static unsigned short
+fts_stat(FTS *sp, FTSENT *p, int follow)
+{
+       FTSENT *t;
+       dev_t dev;
+       ino_t ino;
+       struct stat *sbp, sb;
+       int saved_errno;
+
+       _DIAGASSERT(sp != NULL);
+       _DIAGASSERT(p != NULL);
+
+       /* If user needs stat info, stat buffer already allocated. */
+       sbp = ISSET(FTS_NOSTAT) ? &sb : p->fts_statp;
+
+#ifdef FTS_WHITEOUT
+       /* check for whiteout */
+       if (p->fts_flags & FTS_ISW) {
+               if (sbp != &sb) {
+                       memset(sbp, '\0', sizeof (*sbp));
+                       sbp->st_mode = S_IFWHT;
+               }
+               return (FTS_W);
+       }
+#endif
+
+       /*
+        * 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, set the errno from the stat call.
+        */
+       if (ISSET(FTS_LOGICAL) || follow) {
+               if (stat(p->fts_accpath, sbp)) {
+                       saved_errno = errno;
+                       if (!lstat(p->fts_accpath, sbp)) {
+                               errno = 0;
+                               return (FTS_SLNONE);
+                       }
+                       p->fts_errno = saved_errno;
+                       goto err;
+               }
+       } else if (lstat(p->fts_accpath, sbp)) {
+               p->fts_errno = errno;
+err:           memset(sbp, 0, sizeof(*sbp));
+               return (FTS_NS);
+       }
+
+       if (S_ISDIR(sbp->st_mode)) {
+               /*
+                * Set the device/inode.  Used to find cycles and check for
+                * crossing mount points.  Also remember the link count, used
+                * in fts_build to limit the number of stat calls.  It is
+                * understood that these fields are only referenced if fts_info
+                * is set to FTS_D.
+                */
+               dev = p->fts_dev = sbp->st_dev;
+               ino = p->fts_ino = sbp->st_ino;
+               p->fts_nlink = sbp->st_nlink;
+
+               if (ISDOT(p->fts_name))
+                       return (FTS_DOT);
+
+               /*
+                * Cycle detection is done by brute force when the directory
+                * is first encountered.  If the tree gets deep enough or the
+                * number of symbolic links to directories is high enough,
+                * something faster might be worthwhile.
+                */
+               for (t = p->fts_parent;
+                   t->fts_level >= FTS_ROOTLEVEL; t = t->fts_parent)
+                       if (ino == t->fts_ino && dev == t->fts_dev) {
+                               p->fts_cycle = t;
+                               return (FTS_DC);
+                       }
+               return (FTS_D);
+       }
+       if (S_ISLNK(sbp->st_mode))
+               return (FTS_SL);
+       if (S_ISREG(sbp->st_mode))
+               return (FTS_F);
+       return (FTS_DEFAULT);
+}
+
+static FTSENT *
+fts_sort(FTS *sp, FTSENT *head, size_t nitems)
+{
+       FTSENT **ap, *p;
+
+       _DIAGASSERT(sp != NULL);
+       _DIAGASSERT(head != NULL);
+
+       /*
+        * 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 don't realloc one entry at a time.
+        */
+       if (nitems > sp->fts_nitems) {
+               FTSENT **new;
+
+               new = realloc(sp->fts_array, sizeof(FTSENT *) * (nitems + 40));
+               if (new == 0)
+                       return (head);
+               sp->fts_array = new;
+               sp->fts_nitems = fts_nitems_truncate(nitems + 40);
+       }
+       for (ap = sp->fts_array, p = head; p; p = p->fts_link)
+               *ap++ = p;
+       qsort((void *)sp->fts_array, nitems, sizeof(FTSENT *),
+               (int (*)(const void *, const void *))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 *
+fts_alloc(FTS *sp, const char *name, size_t namelen)
+{
+       FTSENT *p;
+#if defined(FTS_ALLOC_ALIGNED)
+       size_t len;
+#endif
+
+       _DIAGASSERT(sp != NULL);
+       _DIAGASSERT(name != NULL);
+
+#if defined(FTS_ALLOC_ALIGNED)
+       /*
+        * The file name is a variable length array and no stat structure is
+        * necessary if the user has set the nostat bit.  Allocate the FTSENT
+        * structure, the file name and the stat structure in one chunk, but
+        * be careful that the stat structure is reasonably aligned.  Since the
+        * fts_name field is declared to be of size 1, the fts_name pointer is
+        * namelen + 2 before the first possible address of the stat structure.
+        */
+       len = sizeof(FTSENT) + namelen;
+       if (!ISSET(FTS_NOSTAT))
+               len += sizeof(*(p->fts_statp)) + ALIGNBYTES;
+       if ((p = malloc(len)) == NULL)
+               return (NULL);
+
+       if (!ISSET(FTS_NOSTAT))
+               p->fts_statp = (__fts_stat_t *)ALIGN(
+                   (unsigned long)(p->fts_name + namelen + 2));
+#else
+       if ((p = malloc(sizeof(FTSENT) + namelen)) == NULL)
+               return (NULL);
+
+       if (!ISSET(FTS_NOSTAT))
+               if ((p->fts_statp = malloc(sizeof(*(p->fts_statp)))) == NULL) {
+                       free(p);
+                       return (NULL);
+               }
+#endif
+
+        if (ISSET(FTS_NOSTAT))
+                p->fts_statp = NULL;
+
+       /* Copy the name plus the trailing NULL. */
+       memmove(p->fts_name, name, namelen + 1);
+
+       p->fts_namelen = ftsent_namelen_truncate(namelen);
+       p->fts_path = sp->fts_path;
+       p->fts_errno = 0;
+       p->fts_flags = 0;
+       p->fts_instr = FTS_NOINSTR;
+       p->fts_number = 0;
+       p->fts_pointer = NULL;
+       return (p);
+}
+
+static void
+fts_free(FTSENT *p)
+{
+#if !defined(FTS_ALLOC_ALIGNED)
+       if (p->fts_statp)
+               free(p->fts_statp);
+#endif
+       free(p);
+}
+
+static void
+fts_lfree(FTSENT *head)
+{
+       FTSENT *p;
+
+       /* XXX: head may be NULL ? */
+
+       /* Free a linked list of structures. */
+       while ((p = head) != NULL) {
+               head = head->fts_link;
+               fts_free(p);
+       }
+}
+
+static size_t
+fts_pow2(size_t x)
+{
+
+       x--;
+       x |= x>>1;
+       x |= x>>2;
+       x |= x>>4;
+       x |= x>>8;
+       x |= x>>16;
+#if LONG_BIT > 32
+       x |= x>>32;
+#endif
+#if LONG_BIT > 64
+       x |= x>>64;
+#endif
+       x++;
+       return (x);
+}
+
+/*
+ * Allow essentially unlimited paths; find, rm, ls should all work on any tree.
+ * Most systems will allow creation of paths much longer than MAXPATHLEN, even
+ * though the kernel won't resolve them.  Round up the new size to a power of 
2,
+ * so we don't realloc the path 2 bytes at a time.
+ */
+static int
+fts_palloc(FTS *sp, size_t size)
+{
+       char *new;
+
+       _DIAGASSERT(sp != NULL);
+
+#ifdef __FTS_COMPAT_LENGTH
+       /* Protect against fts_pathlen overflow. */
+       if (size > USHRT_MAX + 1) {
+               errno = ENAMETOOLONG;
+               return (1);
+       }
+#endif
+       size = fts_pow2(size);
+       new = realloc(sp->fts_path, size);
+       if (new == 0)
+               return (1);
+       sp->fts_path = new;
+       sp->fts_pathlen = fts_pathlen_truncate(size);
+       return (0);
+}
+
+/*
+ * When the path is realloc'd, have to fix all of the pointers in structures
+ * already returned.
+ */
+static void
+fts_padjust(FTS *sp, FTSENT *head)
+{
+       FTSENT *p;
+       char *addr;
+
+       _DIAGASSERT(sp != NULL);
+
+#define        ADJUST(p) do {                                                  
\
+       if ((p)->fts_accpath != (p)->fts_name)                          \
+               (p)->fts_accpath =                                      \
+                   addr + ((p)->fts_accpath - (p)->fts_path);          \
+       (p)->fts_path = addr;                                           \
+} while (/*CONSTCOND*/0)
+
+       addr = sp->fts_path;
+
+       /* Adjust the current set of children. */
+       for (p = sp->fts_child; p; p = p->fts_link)
+               ADJUST(p);
+
+       /* Adjust the rest of the tree, including the current level. */
+       for (p = head; p->fts_level >= FTS_ROOTLEVEL;) {
+               ADJUST(p);
+               p = p->fts_link ? p->fts_link : p->fts_parent;
+       }
+}
+
+static size_t
+fts_maxarglen(char * const *argv)
+{
+       size_t len, max;
+
+       _DIAGASSERT(argv != NULL);
+
+       for (max = 0; *argv; ++argv)
+               if ((len = strlen(*argv)) > max)
+                       max = len;
+       return (max + 1);
+}
+
+/*
+ * Change to dir specified by fd or p->fts_accpath without getting
+ * tricked by someone changing the world out from underneath us.
+ * Assumes p->fts_dev and p->fts_ino are filled in.
+ */
+static int
+fts_safe_changedir(const FTS *sp, const FTSENT *p, int fd, const char *path)
+{
+       int oldfd = fd, ret = -1;
+       struct stat sb;
+
+       if (ISSET(FTS_NOCHDIR))
+               return 0;
+
+       if (oldfd < 0 && (fd = open(path, O_RDONLY)) == -1)
+               return -1;
+
+       if (fstat(fd, &sb) == -1)
+               goto bail;
+
+       if (sb.st_ino != p->fts_ino || sb.st_dev != p->fts_dev) {
+               errno = ENOENT;
+               goto bail;
+       }
+
+       ret = fchdir(fd);
+
+bail:
+       if (oldfd < 0) {
+               int save_errno = errno;
+               (void)close(fd);
+               errno = save_errno;
+       }
+       return ret;
+}
diff --git a/extension/gawkfts.h b/extension/gawkfts.h
new file mode 100644
index 0000000..755ef98
--- /dev/null
+++ b/extension/gawkfts.h
@@ -0,0 +1,129 @@
+/*     $NetBSD: fts.h,v 1.19 2009/08/16 19:33:38 christos Exp $        */
+
+/*
+ * Copyright (c) 1989, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * 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. 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.
+ *
+ *     @(#)fts.h       8.3 (Berkeley) 8/14/94
+ */
+
+#ifndef        _FTS_H_
+#define        _FTS_H_
+
+typedef struct {
+       struct _ftsent *fts_cur;        /* current node */
+       struct _ftsent *fts_child;      /* linked list of children */
+       struct _ftsent **fts_array;     /* sort array */
+       dev_t fts_dev;                  /* starting device # */
+       char *fts_path;                 /* path for this descent */
+       int fts_rfd;                    /* fd for root */
+       unsigned int fts_pathlen;       /* sizeof(path) */
+       unsigned int fts_nitems;        /* elements in the sort array */
+       int (*fts_compar)               /* compare function */
+               (const struct _ftsent **, const struct _ftsent **);
+
+#define        FTS_COMFOLLOW   0x001           /* follow command line symlinks 
*/
+#define        FTS_LOGICAL     0x002           /* logical walk */
+#define        FTS_NOCHDIR     0x004           /* don't change directories */
+#define        FTS_NOSTAT      0x008           /* don't get stat info */
+#define        FTS_PHYSICAL    0x010           /* physical walk */
+#define        FTS_SEEDOT      0x020           /* return dot and dot-dot */
+#define        FTS_XDEV        0x040           /* don't cross devices */
+// #define     FTS_WHITEOUT    0x080           /* return whiteout information 
*/
+#define        FTS_OPTIONMASK  0x0ff           /* valid user option mask */
+
+#define        FTS_NAMEONLY    0x100           /* (private) child names only */
+#define        FTS_STOP        0x200           /* (private) unrecoverable 
error */
+       int fts_options;                /* fts_open options, global flags */
+} FTS;
+
+typedef struct _ftsent {
+       struct _ftsent *fts_cycle;      /* cycle node */
+       struct _ftsent *fts_parent;     /* parent directory */
+       struct _ftsent *fts_link;       /* next file in directory */
+       long long fts_number;      /* local numeric value */
+       void *fts_pointer;              /* local address value */
+       char *fts_accpath;              /* access path */
+       char *fts_path;                 /* root path */
+       int fts_errno;                  /* errno for this node */
+       int fts_symfd;                  /* fd for symlink */
+       unsigned int fts_pathlen;       /* strlen(fts_path) */
+       unsigned int fts_namelen;       /* strlen(fts_name) */
+
+       ino_t fts_ino;          /* inode */
+       dev_t fts_dev;          /* device */
+       unsigned int fts_nlink; /* link count */
+
+#define        FTS_ROOTPARENTLEVEL     -1
+#define        FTS_ROOTLEVEL            0
+       int fts_level;          /* depth (-1 to N) */
+
+#define        FTS_D            1              /* preorder directory */
+#define        FTS_DC           2              /* directory that causes cycles 
*/
+#define        FTS_DEFAULT      3              /* none of the above */
+#define        FTS_DNR          4              /* unreadable directory */
+#define        FTS_DOT          5              /* dot or dot-dot */
+#define        FTS_DP           6              /* postorder directory */
+#define        FTS_ERR          7              /* error; errno is set */
+#define        FTS_F            8              /* regular file */
+#define        FTS_INIT         9              /* initialized only */
+#define        FTS_NS          10              /* stat(2) failed */
+#define        FTS_NSOK        11              /* no stat(2) requested */
+#define        FTS_SL          12              /* symbolic link */
+#define        FTS_SLNONE      13              /* symbolic link without target 
*/
+#define        FTS_W           14              /* whiteout object */
+       unsigned short fts_info;        /* user flags for FTSENT structure */
+
+#define        FTS_DONTCHDIR    0x01           /* don't chdir .. to the parent 
*/
+#define        FTS_SYMFOLLOW    0x02           /* followed a symlink to get 
here */
+#define        FTS_ISW          0x04           /* this is a whiteout object */
+       unsigned short fts_flags;       /* private flags for FTSENT structure */
+
+#define        FTS_AGAIN        1              /* read node again */
+#define        FTS_FOLLOW       2              /* follow symbolic link */
+#define        FTS_NOINSTR      3              /* no instructions */
+#define        FTS_SKIP         4              /* discard node */
+       unsigned short fts_instr;       /* fts_set() instructions */
+
+       struct stat *fts_statp; /* stat(2) information */
+       char fts_name[1];               /* file name */
+} FTSENT;
+
+#define fts_children gawk_fts_children
+#define fts_close gawk_fts_close
+#define fts_open gawk_fts_open
+#define fts_read gawk_fts_read
+#define fts_set gawk_fts_set
+
+FTSENT *fts_children (FTS *, int);
+int     fts_close (FTS *);
+FTS    *fts_open (char * const *, int,
+                  int (*)(const FTSENT **, const FTSENT **));
+FTSENT *fts_read (FTS *);
+int     fts_set (FTS *, FTSENT *, int) __THROW;
+
+#endif /* !_FTS_H_ */

-----------------------------------------------------------------------

Summary of changes:
 extension/ChangeLog    |    9 +
 extension/Makefile.am  |    5 +-
 extension/Makefile.in  |    8 +-
 extension/README.fts   |   68 +++
 extension/configh.in   |    9 -
 extension/configure    |    4 +-
 extension/configure.ac |    4 +-
 extension/filefuncs.c  |   21 +-
 extension/fts.3        |  773 ++++++++++++++++++++++++++++++
 extension/gawkfts.c    | 1235 ++++++++++++++++++++++++++++++++++++++++++++++++
 extension/gawkfts.h    |  129 +++++
 11 files changed, 2228 insertions(+), 37 deletions(-)
 create mode 100644 extension/README.fts
 create mode 100644 extension/fts.3
 create mode 100644 extension/gawkfts.c
 create mode 100644 extension/gawkfts.h


hooks/post-receive
-- 
gawk



reply via email to

[Prev in Thread] Current Thread [Next in Thread]