summaryrefslogtreecommitdiffstats
path: root/src/lib/nt/fts-nt.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/nt/fts-nt.c')
-rw-r--r--src/lib/nt/fts-nt.c1421
1 files changed, 1421 insertions, 0 deletions
diff --git a/src/lib/nt/fts-nt.c b/src/lib/nt/fts-nt.c
new file mode 100644
index 0000000..5f58abb
--- /dev/null
+++ b/src/lib/nt/fts-nt.c
@@ -0,0 +1,1421 @@
+/* $Id: fts-nt.c 3535 2021-12-20 23:32:28Z bird $ */
+/** @file
+ * Source for the NT port of BSD fts.c.
+ *
+ * @copyright 1990, 1993, 1994 The Regents of the University of California. All rights reserved.
+ * @copyright NT modifications Copyright (C) 2016 knut st. osmundsen <bird-klibc-spam-xiv@anduin.net>
+ * @licenses BSD3
+ *
+ *
+ * Some hints about how the code works.
+ *
+ * The input directories & files are entered into a pseudo root directory and
+ * processed one after another, depth first.
+ *
+ * Directories are completely read into memory first and arranged as linked
+ * list anchored on FTS::fts_cur. fts_read does a pop-like operation on that
+ * list, freeing the nodes after they've been completely processed.
+ * Subdirectories are returned twice by fts_read, the first time when it
+ * decends into it (FTS_D), and the second time as it ascends from it (FTS_DP).
+ *
+ * In parallel to fts_read, there's the fts_children API that fetches the
+ * directory content in a similar manner, but for the consumption of the API
+ * caller rather than FTS itself. The result hangs on FTS::fts_child so it can
+ * be freed when the directory changes or used by fts_read when it is called
+ * upon to enumerate the directory.
+ *
+ *
+ * The NT port of the code does away with the directory changing in favor of
+ * using directory relative opens (present in NT since for ever, just not
+ * exposed thru Win32). A new FTSENT member fts_dirfd has been added to make
+ * this possible for API users too.
+ *
+ * Note! When using Win32 APIs with path input relative to the current
+ * directory, the internal DOS <-> NT path converter will expand it to a
+ * full path and subject it to the 260 char limit.
+ *
+ * The richer NT directory enumeration API allows us to do away with all the
+ * stat() calls, and not have to do link counting and other interesting things
+ * to try speed things up. (You typical stat() implementation on windows is
+ * actually a directory enum call with the name of the file as filter.)
+ */
+
+/*-
+ * 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.
+ * 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.
+ *
+ * $OpenBSD: fts.c,v 1.22 1999/10/03 19:22:22 millert Exp $
+ */
+
+#if 0
+#if defined(LIBC_SCCS) && !defined(lint)
+static char sccsid[] = "@(#)fts.c 8.6 (Berkeley) 8/14/94";
+#endif /* LIBC_SCCS and not lint */
+#endif
+
+#include <errno.h>
+#include "fts-nt.h"
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include "nthlp.h"
+#include "ntdir.h"
+#include "ntopenat.h" /* for AT_FDCWD */
+#include <stdio.h>//debug
+
+static FTSENT *fts_alloc(FTS *sp, char const *name, size_t namelen, wchar_t const *wcsname, size_t cwcname);
+static FTSENT *fts_alloc_ansi(FTS *sp, char const *name, size_t namelen);
+static FTSENT *fts_alloc_utf16(FTS *sp, wchar_t const *wcsname, size_t cwcname);
+static void nt_fts_free_alloc_cache(FTS *sp);
+static FTSENT *fts_build(FTS *, int);
+static void fts_lfree(FTSENT *);
+static void fts_load(FTS *, FTSENT *);
+static size_t fts_maxarglen(char * const *);
+static size_t fts_maxarglenw(wchar_t * const *);
+static void fts_padjust(FTS *, FTSENT *);
+static void fts_padjustw(FTS *, FTSENT *);
+static int fts_palloc(FTS *, size_t, size_t);
+static FTSENT *fts_sort(FTS *, FTSENT *, size_t);
+static int fts_stat(FTS *, FTSENT *, int, HANDLE);
+static int fts_process_stats(FTSENT *, BirdStat_T const *);
+
+#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))
+
+/* fts_build flags */
+#define BCHILD 1 /* fts_children */
+#define BNAMES 2 /* fts_children, names only */
+#define BREAD 3 /* fts_read */
+
+/* NT needs these: */
+#define MAXPATHLEN 260
+#define MAX(a, b) ( (a) >= (b) ? (a) : (b) )
+
+/** Enables BirdDir_T reuse. (Saves malloc and free calls.) */
+#define FTS_WITH_DIRHANDLE_REUSE
+/** Enables allocation statistics. */
+//#define FTS_WITH_STATISTICS
+/** Enables FTSENT allocation cache. */
+#define FTS_WITH_ALLOC_CACHE
+/** Number of size buckets for the FTSENT allocation cache. */
+#define FTS_NUM_FREE_BUCKETS 64
+/** Shift for converting size to free bucket index. */
+#define FTS_FREE_BUCKET_SHIFT 4
+/** The FTSENT allocation alignment. */
+#define FTS_ALIGN_FTSENT (1U << FTS_FREE_BUCKET_SHIFT)
+
+/*
+ * Internal representation of an FTS, including extra implementation
+ * details. The FTS returned from fts_open points to this structure's
+ * ftsp_fts member (and can be cast to an _fts_private as required)
+ */
+struct _fts_private {
+ FTS ftsp_fts;
+#ifdef FTS_WITH_DIRHANDLE_REUSE
+ /** Statically allocate directory handle. */
+ BirdDir_T dirhandle;
+#endif
+#ifdef FTS_WITH_ALLOC_CACHE
+ /** Number of free entries in the above buckets. */
+ size_t numfree;
+# ifdef FTS_WITH_STATISTICS
+ size_t allocs;
+ size_t hits;
+ size_t misses;
+# endif
+ /** Free FTSENT buckets (by size).
+ * This is to avoid hitting the heap, which is a little sluggish on windows. */
+ struct
+ {
+ FTSENT *head;
+ } freebuckets[FTS_NUM_FREE_BUCKETS];
+#endif
+};
+
+
+static FTS * FTSCALL
+nt_fts_open_common(char * const *argv, wchar_t * const *wcsargv, int options,
+ int (*compar)(const FTSENT * const *, const FTSENT * const *))
+{
+ struct _fts_private *priv;
+ FTS *sp;
+ FTSENT *p, *root;
+ FTSENT *parent, *tmp;
+ size_t len, nitems;
+
+ birdResolveImports();
+
+ /* Options check. */
+ if (options & ~FTS_OPTIONMASK) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ /* fts_open() requires at least one path */
+ if (wcsargv ? *wcsargv == NULL : *argv == NULL) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ /* Allocate/initialize the stream. */
+ if ((priv = calloc(1, sizeof(*priv))) == NULL)
+ return (NULL);
+ sp = &priv->ftsp_fts;
+ sp->fts_compar = compar;
+ sp->fts_options = options;
+ SET(FTS_NOCHDIR); /* NT: FTS_NOCHDIR is always on (for external consumes) */
+ sp->fts_cwd_fd = AT_FDCWD;
+
+ /* Shush, GCC. */
+ tmp = NULL;
+
+ /*
+ * Start out with 1K of path space, and enough, in any case,
+ * to hold the user's paths.
+ */
+ if (fts_palloc(sp, MAX(argv ? fts_maxarglen(argv) : 1, MAXPATHLEN),
+ MAX(wcsargv ? fts_maxarglenw(wcsargv) : 1, MAXPATHLEN)) )
+ goto mem1;
+
+ /* Allocate/initialize root's parent. */
+ if ((parent = fts_alloc(sp, NULL, 0, NULL, 0)) == NULL)
+ goto mem2;
+ parent->fts_level = FTS_ROOTPARENTLEVEL;
+
+ /* Allocate/initialize root(s). */
+ for (root = NULL, nitems = 0; wcsargv ? *wcsargv != NULL : *argv != NULL; ++nitems) {
+ /* NT: We need to do some small input transformations to make this and
+ the API user code happy. 1. Lone drive letters get a dot
+ appended so it won't matter if a slash is appended afterwards.
+ 2. DOS slashes are converted to UNIX ones. */
+ wchar_t *wcslash;
+
+ if (wcsargv) {
+ len = wcslen(*wcsargv);
+ if (len == 2 && wcsargv[0][1] == ':') {
+ wchar_t wcsdrive[4];
+ wcsdrive[0] = wcsargv[0][0];
+ wcsdrive[1] = ':';
+ wcsdrive[2] = '.';
+ wcsdrive[3] = '\0';
+ p = fts_alloc_utf16(sp, wcsdrive, 3);
+ } else {
+ p = fts_alloc_utf16(sp, *wcsargv, len);
+ }
+ wcsargv++;
+ } else {
+ len = strlen(*argv);
+ if (len == 2 && argv[0][1] == ':') {
+ char szdrive[4];
+ szdrive[0] = argv[0][0];
+ szdrive[1] = ':';
+ szdrive[2] = '.';
+ szdrive[3] = '\0';
+ p = fts_alloc_ansi(sp, szdrive, 3);
+ } else {
+ p = fts_alloc_ansi(sp, *argv, len);
+ }
+ argv++;
+ }
+ if (p != NULL) { /* likely */ } else { goto mem3; }
+
+ wcslash = wcschr(p->fts_wcsname, '\\');
+ while (wcslash != NULL) {
+ *wcslash++ = '/';
+ wcslash = wcschr(p->fts_wcsname, '\\');
+ }
+
+ if (p->fts_name) {
+ char *slash = strchr(p->fts_name, '\\');
+ while (slash != NULL) {
+ *slash++ = '/';
+ slash = strchr(p->fts_name, '\\');
+ }
+ }
+
+ p->fts_level = FTS_ROOTLEVEL;
+ p->fts_parent = parent;
+ p->fts_accpath = p->fts_name;
+ p->fts_wcsaccpath = p->fts_wcsname;
+ p->fts_info = fts_stat(sp, p, ISSET(FTS_COMFOLLOW), INVALID_HANDLE_VALUE);
+
+ /* 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, NULL, 0, NULL, 0)) == NULL)
+ goto mem3;
+ sp->fts_cur->fts_link = root;
+ sp->fts_cur->fts_info = FTS_INIT;
+
+ return (sp);
+
+mem3:
+ fts_lfree(root);
+ free(parent);
+mem2:
+ free(sp->fts_path);
+ free(sp->fts_wcspath);
+mem1:
+ free(sp);
+ return (NULL);
+}
+
+
+FTS * FTSCALL
+nt_fts_open(char * const *argv, int options,
+ int (*compar)(const FTSENT * const *, const FTSENT * const *))
+{
+ return nt_fts_open_common(argv, NULL, options, compar);
+}
+
+
+FTS * FTSCALL
+nt_fts_openw(wchar_t * const *argv, int options,
+ int (*compar)(const FTSENT * const *, const FTSENT * const *))
+{
+ return nt_fts_open_common(NULL, argv, options, compar);
+}
+
+
+/**
+ * Called by fts_read for FTS_ROOTLEVEL entries only.
+ */
+static void
+fts_load(FTS *sp, FTSENT *p)
+{
+ size_t len;
+ wchar_t *pwc;
+
+ /*
+ * 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.
+ */
+ if (!(sp->fts_options & FTS_NO_ANSI)) {
+ char *cp;
+ len = p->fts_pathlen = p->fts_namelen;
+ memmove(sp->fts_path, p->fts_name, len + 1);
+ cp = strrchr(p->fts_name, '/');
+ if (cp != NULL && (cp != p->fts_name || cp[1])) {
+ len = strlen(++cp);
+ memmove(p->fts_name, cp, len + 1);
+ p->fts_namelen = len;
+ }
+ p->fts_accpath = p->fts_path = sp->fts_path;
+ }
+
+ len = p->fts_cwcpath = p->fts_cwcname;
+ memmove(sp->fts_wcspath, p->fts_wcsname, (len + 1) * sizeof(wchar_t));
+ pwc = wcsrchr(p->fts_wcsname, '/');
+ if (pwc != NULL && (pwc != p->fts_wcsname || pwc[1])) {
+ len = wcslen(++pwc);
+ memmove(p->fts_wcsname, pwc, (len + 1) * sizeof(wchar_t));
+ p->fts_cwcname = len;
+ }
+ p->fts_wcsaccpath = p->fts_wcspath = sp->fts_wcspath;
+
+ sp->fts_dev = p->fts_dev;
+}
+
+
+int FTSCALL
+nt_fts_close(FTS *sp)
+{
+ FTSENT *freep, *p;
+ /*int saved_errno;*/
+
+ /*
+ * 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) {
+ for (p = sp->fts_cur; p->fts_level >= FTS_ROOTLEVEL;) {
+ freep = p;
+ p = p->fts_link != NULL ? p->fts_link : p->fts_parent;
+ free(freep);
+ }
+ 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);
+ free(sp->fts_wcspath);
+#ifdef FTS_WITH_ALLOC_CACHE
+# ifdef FTS_WITH_STATISTICS
+ {
+ struct _fts_private *priv = (struct _fts_private *)sp;
+ fprintf(stderr, "numfree=%u allocs=%u hits=%u (%uppt) misses=%u (%uppt) other=%u\n",
+ priv->numfree, priv->allocs,
+ priv->hits, (unsigned)((double)priv->hits * 1000.0 / priv->allocs),
+ priv->misses, (unsigned)((double)priv->misses * 1000.0 / priv->allocs),
+ priv->allocs - priv->misses - priv->hits);
+ }
+# endif
+#endif
+ nt_fts_free_alloc_cache(sp);
+#ifdef FTS_WITH_DIRHANDLE_REUSE
+ birdDirClose(&((struct _fts_private *)sp)->dirhandle);
+#endif
+
+ /* Free up the stream pointer. */
+ free(sp);
+ return (0);
+}
+
+
+/**
+ * Frees a FTSENT structure by way of the allocation cache.
+ */
+static void
+fts_free_entry(FTS *sp, FTSENT *tmp)
+{
+ if (tmp != NULL) {
+ struct _fts_private *priv = (struct _fts_private *)sp;
+#ifdef FTS_WITH_ALLOC_CACHE
+ size_t idx;
+#endif
+
+ if (tmp->fts_dirfd == INVALID_HANDLE_VALUE) {
+ /* There are probably more files than directories out there. */
+ } else {
+ birdCloseFile(tmp->fts_dirfd);
+ tmp->fts_dirfd = INVALID_HANDLE_VALUE;
+ }
+
+#ifdef FTS_WITH_ALLOC_CACHE
+ idx = (tmp->fts_alloc_size - sizeof(FTSENT)) >> FTS_FREE_BUCKET_SHIFT;
+ if (idx < FTS_NUM_FREE_BUCKETS) {
+ tmp->fts_link = priv->freebuckets[idx].head;
+ priv->freebuckets[idx].head = tmp;
+ } else {
+ tmp->fts_link = priv->freebuckets[FTS_NUM_FREE_BUCKETS - 1].head;
+ priv->freebuckets[FTS_NUM_FREE_BUCKETS - 1].head = tmp;
+ }
+
+ priv->numfree++;
+#else
+ free(tmp);
+#endif
+ }
+}
+
+
+/*
+ * 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_pathlen - (p->fts_path[p->fts_pathlen - 1] == '/') )
+#define NAPPENDW(p) ( p->fts_cwcpath - (p->fts_wcspath[p->fts_cwcpath - 1] == L'/') )
+
+FTSENT * FTSCALL
+nt_fts_read(FTS *sp)
+{
+ FTSENT *p, *tmp;
+ int instr;
+ wchar_t *pwc;
+
+ /* Set current node pointer. */
+ p = sp->fts_cur;
+
+ /* If finished or unrecoverable error, return NULL. */
+ if (p != NULL && !ISSET(FTS_STOP)) {
+ /* likely */
+ } else {
+ return (NULL);
+ }
+
+ /* 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) {
+ /* likely */
+ } else {
+ p->fts_info = fts_stat(sp, p, 0, INVALID_HANDLE_VALUE);
+ 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.
+ *
+ * NT: Since we don't change directory, we just set FTS_SYMFOLLOW
+ * here in case a API client checks it.
+ */
+ if ( instr != FTS_FOLLOW
+ || (p->fts_info != FTS_SL && p->fts_info != FTS_SLNONE)) {
+ /* likely */
+ } else {
+ p->fts_info = fts_stat(sp, p, 1, INVALID_HANDLE_VALUE);
+ if (p->fts_info == FTS_D /*&& !ISSET(FTS_NOCHDIR)*/) {
+ 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 (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 != NULL && 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 == NULL) {
+ p = fts_build(sp, BREAD);
+ if (p != NULL) {
+ /* likely */
+ } else {
+ if (ISSET(FTS_STOP))
+ return (NULL);
+ return sp->fts_cur;
+ }
+
+ } else {
+ 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) {
+ /*
+ * If reached the top, return to the original directory (or
+ * the root of the tree), and load the paths for the next root.
+ */
+ if (p->fts_level != FTS_ROOTLEVEL) {
+ /* likely */
+ } else {
+ fts_free_entry(sp, tmp);
+ 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) {
+ /* likely */
+ } else {
+ fts_free_entry(sp, tmp);
+ goto next;
+ }
+ if (p->fts_instr != FTS_FOLLOW) {
+ /* likely */
+ } else {
+ p->fts_info = fts_stat(sp, p, 1, INVALID_HANDLE_VALUE);
+ /* NT: See above regarding fts_flags. */
+ if (p->fts_info == FTS_D) {
+ p->fts_flags |= FTS_SYMFOLLOW;
+ }
+ p->fts_instr = FTS_NOINSTR;
+ }
+
+ fts_free_entry(sp, tmp);
+
+name:
+ if (!(sp->fts_options & FTS_NO_ANSI)) {
+ char *t = sp->fts_path + NAPPEND(p->fts_parent);
+ *t++ = '/';
+ memmove(t, p->fts_name, p->fts_namelen + 1);
+ }
+ pwc = sp->fts_wcspath + NAPPENDW(p->fts_parent);
+ *pwc++ = '/';
+ memmove(pwc, p->fts_wcsname, (p->fts_cwcname + 1) * sizeof(wchar_t));
+ return (sp->fts_cur = p);
+ }
+
+ /* Move up to the parent node. */
+ p = tmp->fts_parent;
+
+ if (p->fts_level != FTS_ROOTPARENTLEVEL) {
+ /* likely */
+ } else {
+ /*
+ * Done; free everything up and set errno to 0 so the user
+ * can distinguish between error and EOF.
+ */
+ fts_free_entry(sp, tmp);
+ fts_free_entry(sp, p);
+ errno = 0;
+ return (sp->fts_cur = NULL);
+ }
+
+ /* NUL terminate the pathname. */
+ if (!(sp->fts_options & FTS_NO_ANSI))
+ sp->fts_path[p->fts_pathlen] = '\0';
+ sp->fts_wcspath[ p->fts_cwcpath] = '\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.
+ *
+ * NT: We're doing no fchdir, but we need to close the directory handle.
+ */
+ if (p->fts_dirfd != INVALID_HANDLE_VALUE) {
+ birdCloseFile(p->fts_dirfd);
+ p->fts_dirfd = INVALID_HANDLE_VALUE;
+ }
+ fts_free_entry(sp, tmp);
+ 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 FTSCALL
+nt_fts_set(FTS *sp, FTSENT *p, int instr)
+{
+ if (instr != 0 && instr != FTS_AGAIN && instr != FTS_FOLLOW &&
+ instr != FTS_NOINSTR && instr != FTS_SKIP) {
+ errno = EINVAL;
+ return (1);
+ }
+ p->fts_instr = instr;
+ return (0);
+}
+
+FTSENT * FTSCALL
+nt_fts_children(FTS *sp, int instr)
+{
+ FTSENT *p;
+
+ if (instr != 0 && 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 != NULL) {
+ fts_lfree(sp->fts_child);
+ sp->fts_child = NULL; /* (bird - double free for _open(".") failure in original) */
+ }
+
+ /* NT: Some BSD utility sets FTS_NAMEONLY? We don't really need this
+ optimization, but since it only hurts that utility, it can stay. */
+ if (instr == FTS_NAMEONLY) {
+ assert(0); /* don't specify FTS_NAMEONLY on NT. */
+ SET(FTS_NAMEONLY);
+ instr = BNAMES;
+ } else
+ instr = BCHILD;
+
+ return (sp->fts_child = fts_build(sp, instr));
+}
+
+#ifndef fts_get_clientptr
+#error "fts_get_clientptr not defined"
+#endif
+
+void *
+(FTSCALL fts_get_clientptr)(FTS *sp)
+{
+
+ return (fts_get_clientptr(sp));
+}
+
+#ifndef fts_get_stream
+#error "fts_get_stream not defined"
+#endif
+
+FTS *
+(FTSCALL fts_get_stream)(FTSENT *p)
+{
+ return (fts_get_stream(p));
+}
+
+void FTSCALL
+nt_fts_set_clientptr(FTS *sp, void *clientptr)
+{
+
+ sp->fts_clientptr = clientptr;
+}
+
+/*
+ * 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.
+ *
+ * NT: We do not do any link counting or stat avoiding, which invalidates the
+ * above warnings. This function is very simple for us.
+ */
+static FTSENT *
+fts_build(FTS *sp, int type)
+{
+ BirdDirEntryW_T *dp;
+ FTSENT *p, *cur;
+ FTSENT * volatile head,* volatile *tailp; /* volatile is to prevent aliasing trouble */
+ DIR *dirp;
+ int saved_errno, doadjust, doadjust_utf16;
+ long level;
+ size_t len, cwcdir, maxlen, cwcmax, nitems;
+ unsigned fDirOpenFlags;
+
+ /* 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.
+ *
+ * NT: We do a two stage open so we can keep the directory handle around
+ * after we've enumerated the directory. The dir handle is used by
+ * us here and by the API users to more efficiently and safely open
+ * members of the directory.
+ */
+ fDirOpenFlags = BIRDDIR_F_EXTRA_INFO | BIRDDIR_F_KEEP_HANDLE;
+ if (cur->fts_dirfd == INVALID_HANDLE_VALUE) {
+ if (cur->fts_parent->fts_dirfd != INVALID_HANDLE_VALUE) {
+ /* (This works fine for symlinks too, since we follow them.) */
+ cur->fts_dirfd = birdOpenFileExW(cur->fts_parent->fts_dirfd,
+ cur->fts_wcsname,
+ FILE_READ_DATA | SYNCHRONIZE,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ FILE_OPEN,
+ FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT,
+ OBJ_CASE_INSENSITIVE);
+ } else {
+ cur->fts_dirfd = birdOpenFileW(cur->fts_wcsaccpath,
+ FILE_READ_DATA | SYNCHRONIZE,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ FILE_OPEN,
+ FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT,
+ OBJ_CASE_INSENSITIVE);
+ }
+ if (cur->fts_dirfd != INVALID_HANDLE_VALUE) { /* likely */ }
+ else goto l_open_err;
+
+ } else {
+ fDirOpenFlags |= BIRDDIR_F_RESTART_SCAN;
+ }
+#ifdef FTS_WITH_DIRHANDLE_REUSE
+ dirp = birdDirOpenFromHandleWithReuse(&((struct _fts_private *)sp)->dirhandle, cur->fts_dirfd, NULL,
+ fDirOpenFlags | BIRDDIR_F_STATIC_ALLOC);
+#else
+ dirp = birdDirOpenFromHandle(cur->fts_dirfd, NULL, fDirOpenFlags);
+#endif
+ if (dirp == NULL) {
+l_open_err:
+ if (type == BREAD) {
+ cur->fts_info = FTS_DNR;
+ cur->fts_errno = errno;
+ }
+ return (NULL);
+ }
+
+ /*
+ * 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 (sp->fts_options & FTS_NO_ANSI) {
+ len = 0;
+ maxlen = 0x10000;
+ } else {
+ len = NAPPEND(cur);
+ len++;
+ maxlen = sp->fts_pathlen - len;
+ }
+
+ cwcdir = NAPPENDW(cur);
+ cwcdir++;
+ cwcmax = sp->fts_cwcpath - len;
+
+ level = cur->fts_level + 1;
+
+ /* Read the directory, attaching each entry to the `link' pointer. */
+ doadjust = doadjust_utf16 = 0;
+ nitems = 0;
+ head = NULL;
+ tailp = &head;
+ while ((dp = birdDirReadW(dirp)) != NULL) {
+ if (ISSET(FTS_SEEDOT) || !ISDOT(dp->d_name)) {
+ /* assume dirs have two or more entries */
+ } else {
+ continue;
+ }
+
+ if ((p = fts_alloc_utf16(sp, dp->d_name, dp->d_namlen)) != NULL) {
+ /* likely */
+ } else {
+ goto mem1;
+ }
+
+ /* include space for NUL */
+ if (p->fts_namelen < maxlen && p->fts_cwcname < cwcmax) {
+ /* likely */
+ } else {
+ void *oldaddr = sp->fts_path;
+ wchar_t *oldwcspath = sp->fts_wcspath;
+ if (fts_palloc(sp,
+ p->fts_namelen >= maxlen ? len + p->fts_namelen + 1 : 0,
+ p->fts_cwcname >= cwcmax ? cwcdir + p->fts_cwcname + 1 : 0)) {
+mem1:
+ /*
+ * No more memory for path or structures. Save
+ * errno, free up the current structure and the
+ * structures already allocated.
+ */
+ saved_errno = errno;
+ if (p)
+ free(p);
+ fts_lfree(head);
+#ifndef FTS_WITH_DIRHANDLE_REUSE
+ birdDirClose(dirp);
+#endif
+ birdCloseFile(cur->fts_dirfd);
+ cur->fts_dirfd = INVALID_HANDLE_VALUE;
+ cur->fts_info = FTS_ERR;
+ SET(FTS_STOP);
+ errno = saved_errno;
+ return (NULL);
+ }
+ /* Did realloc() change the pointer? */
+ doadjust |= oldaddr != sp->fts_path;
+ doadjust_utf16 |= oldwcspath != sp->fts_wcspath;
+ maxlen = sp->fts_pathlen - len;
+ cwcmax = sp->fts_cwcpath - cwcdir;
+ }
+
+ p->fts_level = level;
+ p->fts_parent = sp->fts_cur;
+ p->fts_pathlen = len + p->fts_namelen;
+ p->fts_cwcpath = cwcdir + p->fts_cwcname;
+ p->fts_accpath = p->fts_path;
+ p->fts_wcsaccpath = p->fts_wcspath;
+ p->fts_stat = dp->d_stat;
+ p->fts_info = fts_process_stats(p, &dp->d_stat);
+
+ /* We walk in directory order so "ls -f" doesn't get upset. */
+ p->fts_link = NULL;
+ *tailp = p;
+ tailp = &p->fts_link;
+ ++nitems;
+ }
+
+#ifndef FTS_WITH_DIRHANDLE_REUSE
+ birdDirClose(dirp);
+#endif
+
+ /*
+ * If realloc() changed the address of the path, adjust the
+ * addresses for the rest of the tree and the dir list.
+ */
+ if (doadjust)
+ fts_padjust(sp, head);
+ if (doadjust_utf16)
+ fts_padjustw(sp, head);
+
+ /* 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);
+}
+
+
+/**
+ * @note Only used on NT with input arguments, FTS_AGAIN, and links that needs
+ * following. On link information is generally retrieved during directory
+ * enumeration on NT, in line with it's DOS/OS2/FAT API heritage.
+ */
+static int
+fts_stat(FTS *sp, FTSENT *p, int follow, HANDLE dfd)
+{
+ int saved_errno;
+ const wchar_t *wcspath;
+
+ if (dfd == INVALID_HANDLE_VALUE) {
+ wcspath = p->fts_wcsaccpath;
+ } else {
+ wcspath = p->fts_wcsname;
+ }
+
+ /*
+ * 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 (birdStatAtW(dfd, wcspath, &p->fts_stat, 1 /*fFollowLink*/)) {
+ saved_errno = errno;
+ if (birdStatAtW(dfd, wcspath, &p->fts_stat, 0 /*fFollowLink*/)) {
+ p->fts_errno = saved_errno;
+ goto err;
+ }
+ errno = 0;
+ if (S_ISLNK(p->fts_stat.st_mode))
+ return (FTS_SLNONE);
+ }
+ } else if (birdStatAtW(dfd, wcspath, &p->fts_stat, 0 /*fFollowLink*/)) {
+ p->fts_errno = errno;
+err: memset(&p->fts_stat, 0, sizeof(struct stat));
+ return (FTS_NS);
+ }
+ return fts_process_stats(p, &p->fts_stat);
+}
+
+/* Shared between fts_stat and fts_build. */
+static int
+fts_process_stats(FTSENT *p, BirdStat_T const *sbp)
+{
+ if (S_ISDIR(sbp->st_mode)) {
+ FTSENT *t;
+ fts_dev_t dev;
+ fts_ino_t ino;
+
+ /*
+ * 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_wcsname))
+ 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);
+}
+
+/*
+ * The comparison function takes pointers to pointers to FTSENT structures.
+ * Qsort wants a comparison function that takes pointers to void.
+ * (Both with appropriate levels of const-poisoning, of course!)
+ * Use a trampoline function to deal with the difference.
+ */
+static int
+fts_compar(const void *a, const void *b)
+{
+ FTS *parent;
+
+ parent = (*(const FTSENT * const *)a)->fts_fts;
+ return (*parent->fts_compar)(a, b);
+}
+
+static FTSENT *
+fts_sort(FTS *sp, FTSENT *head, size_t nitems)
+{
+ FTSENT **ap, *p;
+
+ /*
+ * 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) {
+ void *ptr;
+ sp->fts_nitems = nitems + 40;
+ ptr = realloc(sp->fts_array, sp->fts_nitems * sizeof(FTSENT *));
+ if (ptr != NULL) {
+ sp->fts_array = ptr;
+ } else {
+ free(sp->fts_array);
+ sp->fts_array = NULL;
+ sp->fts_nitems = 0;
+ return (head);
+ }
+ }
+ for (ap = sp->fts_array, p = head; p; p = p->fts_link)
+ *ap++ = p;
+ qsort(sp->fts_array, nitems, sizeof(FTSENT *), 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, char const *name, size_t namelen, wchar_t const *wcsname, size_t cwcname)
+{
+ struct _fts_private *priv = (struct _fts_private *)sp;
+ FTSENT *p;
+ size_t len;
+#ifdef FTS_WITH_ALLOC_CACHE
+ size_t aligned;
+ size_t idx;
+#endif
+
+#if defined(FTS_WITH_STATISTICS) && defined(FTS_WITH_ALLOC_CACHE)
+ priv->allocs++;
+#endif
+ /*
+ * The file name is a variable length array. Allocate the FTSENT
+ * structure and the file name.
+ */
+ len = sizeof(FTSENT) + (cwcname + 1) * sizeof(wchar_t);
+ if (!(sp->fts_options & FTS_NO_ANSI))
+ len += namelen + 1;
+
+ /*
+ * To speed things up we cache entries. This code is a little insane,
+ * but that's preferable to slow code.
+ */
+#ifdef FTS_WITH_ALLOC_CACHE
+ aligned = (len + FTS_ALIGN_FTSENT + 1) & ~(size_t)(FTS_ALIGN_FTSENT - 1);
+ idx = ((aligned - sizeof(FTSENT)) >> FTS_FREE_BUCKET_SHIFT);
+ if ( idx < FTS_NUM_FREE_BUCKETS
+ && (p = priv->freebuckets[idx].head)
+ && p->fts_alloc_size >= len) {
+ priv->freebuckets[idx].head = p->fts_link;
+ priv->numfree--;
+# ifdef FTS_WITH_STATISTICS
+ priv->hits++;
+# endif
+
+ } else {
+# ifdef FTS_WITH_STATISTICS
+ priv->misses++;
+# endif
+ p = malloc(aligned);
+ if (p) {
+ p->fts_alloc_size = (unsigned)aligned;
+ } else {
+ nt_fts_free_alloc_cache(sp);
+ p = malloc(len);
+ if (!p)
+ return NULL;
+ p->fts_alloc_size = (unsigned)len;
+ }
+ }
+#else /* !FTS_WITH_ALLOC_CACHE */
+ p = malloc(len);
+ if (p) {
+ p->fts_alloc_size = (unsigned)len;
+ } else {
+ return NULL;
+ }
+#endif /* !FTS_WITH_ALLOC_CACHE */
+
+ /* Copy the names and guarantee NUL termination. */
+ p->fts_wcsname = (wchar_t *)(p + 1);
+ memcpy(p->fts_wcsname, wcsname, cwcname * sizeof(wchar_t));
+ p->fts_wcsname[cwcname] = '\0';
+ p->fts_cwcname = cwcname;
+ if (!(sp->fts_options & FTS_NO_ANSI)) {
+ p->fts_name = (char *)(p->fts_wcsname + cwcname + 1);
+ memcpy(p->fts_name, name, namelen);
+ p->fts_name[namelen] = '\0';
+ p->fts_namelen = namelen;
+ } else {
+ p->fts_name = NULL;
+ p->fts_namelen = 0;
+ }
+
+ p->fts_path = sp->fts_path;
+ p->fts_wcspath = sp->fts_wcspath;
+ p->fts_statp = &p->fts_stat;
+ p->fts_errno = 0;
+ p->fts_flags = 0;
+ p->fts_instr = FTS_NOINSTR;
+ p->fts_number = 0;
+ p->fts_pointer = NULL;
+ p->fts_fts = sp;
+ p->fts_dirfd = INVALID_HANDLE_VALUE;
+ return (p);
+}
+
+
+/**
+ * Converts the ANSI name to UTF-16 and calls fts_alloc.
+ *
+ * @returns Pointer to allocated and mostly initialized FTSENT structure on
+ * success. NULL on failure, caller needs to record it.
+ * @param sp Pointer to FTS instance.
+ * @param name The ANSI name.
+ * @param namelen The ANSI name length.
+ */
+static FTSENT *
+fts_alloc_ansi(FTS *sp, char const *name, size_t namelen)
+{
+ MY_UNICODE_STRING UniStr;
+ MY_ANSI_STRING AnsiStr;
+ MY_NTSTATUS rcNt;
+ FTSENT *pRet;
+
+ UniStr.Buffer = NULL;
+ UniStr.MaximumLength = UniStr.Length = 0;
+
+ AnsiStr.Buffer = (char *)name;
+ AnsiStr.Length = AnsiStr.MaximumLength = (USHORT)namelen;
+
+ rcNt = g_pfnRtlAnsiStringToUnicodeString(&UniStr, &AnsiStr, TRUE /*fAllocate*/);
+ if (NT_SUCCESS(rcNt)) {
+ pRet = fts_alloc(sp, name, namelen, UniStr.Buffer, UniStr.Length / sizeof(wchar_t));
+ HeapFree(GetProcessHeap(), 0, UniStr.Buffer);
+ } else {
+ pRet = NULL;
+ }
+ return pRet;
+}
+
+
+/**
+ * Converts the UTF-16 name to ANSI (if necessary) and calls fts_alloc.
+ *
+ * @returns Pointer to allocated and mostly initialized FTSENT structure on
+ * success. NULL on failure, caller needs to record it.
+ * @param sp Pointer to the FTS instance.
+ * @param wcsname The UTF-16 name.
+ * @param cwcname The UTF-16 name length.
+ */
+static FTSENT *
+fts_alloc_utf16(FTS *sp, wchar_t const *wcsname, size_t cwcname)
+{
+ FTSENT *pRet;
+
+ if (sp->fts_options & FTS_NO_ANSI) {
+ pRet = fts_alloc(sp, NULL, 0, wcsname, cwcname);
+ } else {
+ MY_UNICODE_STRING UniStr;
+ MY_ANSI_STRING AnsiStr;
+ MY_NTSTATUS rcNt;
+
+ UniStr.Buffer = (wchar_t *)wcsname;
+ UniStr.MaximumLength = UniStr.Length = (USHORT)(cwcname * sizeof(wchar_t));
+
+ AnsiStr.Buffer = NULL;
+ AnsiStr.Length = AnsiStr.MaximumLength = 0;
+
+ rcNt = g_pfnRtlUnicodeStringToAnsiString(&AnsiStr, &UniStr, TRUE /*fAllocate*/);
+ if (NT_SUCCESS(rcNt)) {
+ pRet = fts_alloc(sp, AnsiStr.Buffer, AnsiStr.Length, wcsname, cwcname);
+ HeapFree(GetProcessHeap(), 0, AnsiStr.Buffer);
+ } else {
+ pRet = NULL;
+ }
+ }
+ return pRet;
+}
+
+
+/**
+ * Frees up the FTSENT allocation cache.
+ *
+ * Used by nt_fts_close, but also called by fts_alloc on alloc failure.
+ *
+ * @param sp Pointer to the FTS instance.
+ */
+static void nt_fts_free_alloc_cache(FTS *sp)
+{
+#ifdef FTS_WITH_ALLOC_CACHE
+ struct _fts_private *priv = (struct _fts_private *)sp;
+ unsigned i = K_ELEMENTS(priv->freebuckets);
+ while (i-- > 0) {
+ FTSENT *cur = priv->freebuckets[i].head;
+ priv->freebuckets[i].head = NULL;
+ while (cur) {
+ FTSENT *freeit = cur;
+ cur = cur->fts_link;
+ free(freeit);
+ }
+ }
+ priv->numfree = 0;
+#else
+ (void)sp;
+#endif
+}
+
+
+static void
+fts_lfree(FTSENT *head)
+{
+ FTSENT *p;
+
+ /* Free a linked list of structures. */
+ while ((p = head)) {
+ head = head->fts_link;
+ assert(p->fts_dirfd == INVALID_HANDLE_VALUE);
+ free(p);
+ }
+}
+
+/*
+ * 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. Add the size (not just what's needed)
+ * plus 256 bytes so don't realloc the path 2 bytes at a time.
+ */
+static int
+fts_palloc(FTS *sp, size_t more, size_t cwcmore)
+{
+ void *ptr;
+
+ /** @todo Isn't more and cwcmore minimum buffer sizes rather than what needs
+ * to be added to the buffer?? This code makes no sense when looking at
+ * the way the caller checks things out! */
+
+ if (more) {
+ sp->fts_pathlen += more + 256;
+ ptr = realloc(sp->fts_path, sp->fts_pathlen);
+ if (ptr) {
+ sp->fts_path = ptr;
+ } else {
+ free(sp->fts_path);
+ sp->fts_path = NULL;
+ free(sp->fts_wcspath);
+ sp->fts_wcspath = NULL;
+ return 1;
+ }
+ }
+
+ if (cwcmore) {
+ sp->fts_cwcpath += cwcmore + 256;
+ ptr = realloc(sp->fts_wcspath, sp->fts_cwcpath);
+ if (ptr) {
+ sp->fts_wcspath = ptr;
+ } else {
+ free(sp->fts_path);
+ sp->fts_path = NULL;
+ free(sp->fts_wcspath);
+ sp->fts_wcspath = NULL;
+ return 1;
+ }
+ }
+ 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 = sp->fts_path;
+
+#define ADJUST(p) do { \
+ if ((p)->fts_accpath != (p)->fts_name) { \
+ (p)->fts_accpath = \
+ (char *)addr + ((p)->fts_accpath - (p)->fts_path); \
+ } \
+ (p)->fts_path = addr; \
+} while (0)
+ /* 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;
+ }
+}
+
+/*
+ * When the UTF-16 path is realloc'd, have to fix all of the pointers in
+ * structures already returned.
+ */
+static void
+fts_padjustw(FTS *sp, FTSENT *head)
+{
+ FTSENT *p;
+ wchar_t *addr = sp->fts_wcspath;
+
+#define ADJUSTW(p) \
+ do { \
+ if ((p)->fts_wcsaccpath != (p)->fts_wcsname) \
+ (p)->fts_wcsaccpath = addr + ((p)->fts_wcsaccpath - (p)->fts_wcspath); \
+ (p)->fts_wcspath = addr; \
+ } while (0)
+
+ /* Adjust the current set of children. */
+ for (p = sp->fts_child; p; p = p->fts_link)
+ ADJUSTW(p);
+
+ /* Adjust the rest of the tree, including the current level. */
+ for (p = head; p->fts_level >= FTS_ROOTLEVEL;) {
+ ADJUSTW(p);
+ p = p->fts_link ? p->fts_link : p->fts_parent;
+ }
+}
+
+static size_t
+fts_maxarglen(char * const *argv)
+{
+ size_t len, max;
+
+ for (max = 0; *argv; ++argv)
+ if ((len = strlen(*argv)) > max)
+ max = len;
+ return (max + 1);
+}
+
+/** Returns the max string size (including term). */
+static size_t
+fts_maxarglenw(wchar_t * const *argv)
+{
+ size_t max = 0;
+ for (; *argv; ++argv) {
+ size_t len = wcslen(*argv);
+ if (len > max)
+ max = len;
+ }
+ return max + 1;
+}
+