about summary refs log tree commit diff
path: root/REORG.TODO/io/ftw.c
diff options
context:
space:
mode:
authorZack Weinberg <zackw@panix.com>2017-06-08 15:39:03 -0400
committerZack Weinberg <zackw@panix.com>2017-06-08 15:39:03 -0400
commit5046dbb4a7eba5eccfd258f92f4735c9ffc8d069 (patch)
tree4470480d904b65cf14ca524f96f79eca818c3eaf /REORG.TODO/io/ftw.c
parent199fc19d3aaaf57944ef036e15904febe877fc93 (diff)
downloadglibc-5046dbb4a7eba5eccfd258f92f4735c9ffc8d069.tar.gz
glibc-5046dbb4a7eba5eccfd258f92f4735c9ffc8d069.tar.xz
glibc-5046dbb4a7eba5eccfd258f92f4735c9ffc8d069.zip
Prepare for radical source tree reorganization. zack/build-layout-experiment
All top-level files and directories are moved into a temporary storage
directory, REORG.TODO, except for files that will certainly still
exist in their current form at top level when we're done (COPYING,
COPYING.LIB, LICENSES, NEWS, README), all old ChangeLog files (which
are moved to the new directory OldChangeLogs, instead), and the
generated file INSTALL (which is just deleted; in the new order, there
will be no generated files checked into version control).
Diffstat (limited to 'REORG.TODO/io/ftw.c')
-rw-r--r--REORG.TODO/io/ftw.c867
1 files changed, 867 insertions, 0 deletions
diff --git a/REORG.TODO/io/ftw.c b/REORG.TODO/io/ftw.c
new file mode 100644
index 0000000000..140a237746
--- /dev/null
+++ b/REORG.TODO/io/ftw.c
@@ -0,0 +1,867 @@
+/* File tree walker functions.
+   Copyright (C) 1996-2017 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+   Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#if __GNUC__
+# define alloca __builtin_alloca
+#else
+# if HAVE_ALLOCA_H
+#  include <alloca.h>
+# else
+#  ifdef _AIX
+ #  pragma alloca
+#  else
+char *alloca ();
+#  endif
+# endif
+#endif
+
+#ifdef _LIBC
+# include <dirent.h>
+# define NAMLEN(dirent) _D_EXACT_NAMLEN (dirent)
+#else
+# if HAVE_DIRENT_H
+#  include <dirent.h>
+#  define NAMLEN(dirent) strlen ((dirent)->d_name)
+# else
+#  define dirent direct
+#  define NAMLEN(dirent) (dirent)->d_namlen
+#  if HAVE_SYS_NDIR_H
+#   include <sys/ndir.h>
+#  endif
+#  if HAVE_SYS_DIR_H
+#   include <sys/dir.h>
+#  endif
+#  if HAVE_NDIR_H
+#   include <ndir.h>
+#  endif
+# endif
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <ftw.h>
+#include <limits.h>
+#include <search.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <not-cancel.h>
+#include <sys/param.h>
+#ifdef _LIBC
+# include <include/sys/stat.h>
+#else
+# include <sys/stat.h>
+#endif
+
+#if ! _LIBC && !HAVE_DECL_STPCPY && !defined stpcpy
+char *stpcpy ();
+#endif
+
+#if ! _LIBC && ! defined HAVE_MEMPCPY && ! defined mempcpy
+/* Be CAREFUL that there are no side effects in N.  */
+# define mempcpy(D, S, N) ((void *) ((char *) memcpy (D, S, N) + (N)))
+#endif
+
+/* #define NDEBUG 1 */
+#include <assert.h>
+
+#ifndef _LIBC
+# undef __chdir
+# define __chdir chdir
+# undef __closedir
+# define __closedir closedir
+# undef __fchdir
+# define __fchdir fchdir
+# undef __getcwd
+# define __getcwd(P, N) xgetcwd ()
+extern char *xgetcwd (void);
+# undef __mempcpy
+# define __mempcpy mempcpy
+# undef __opendir
+# define __opendir opendir
+# undef __readdir64
+# define __readdir64 readdir
+# undef __stpcpy
+# define __stpcpy stpcpy
+# undef __tdestroy
+# define __tdestroy tdestroy
+# undef __tfind
+# define __tfind tfind
+# undef __tsearch
+# define __tsearch tsearch
+# undef internal_function
+# define internal_function /* empty */
+# undef dirent64
+# define dirent64 dirent
+# undef MAX
+# define MAX(a, b) ((a) > (b) ? (a) : (b))
+#endif
+
+/* Arrange to make lstat calls go through the wrapper function
+   on systems with an lstat function that does not dereference symlinks
+   that are specified with a trailing slash.  */
+#if ! _LIBC && ! LSTAT_FOLLOWS_SLASHED_SYMLINK
+int rpl_lstat (const char *, struct stat *);
+# undef lstat
+# define lstat(Name, Stat_buf) rpl_lstat(Name, Stat_buf)
+#endif
+
+#ifndef __set_errno
+# define __set_errno(Val) errno = (Val)
+#endif
+
+/* Support for the LFS API version.  */
+#ifndef FTW_NAME
+# define FTW_NAME ftw
+# define NFTW_NAME nftw
+# define NFTW_OLD_NAME __old_nftw
+# define NFTW_NEW_NAME __new_nftw
+# define INO_T ino_t
+# define STAT stat
+# ifdef _LIBC
+#  define LXSTAT __lxstat
+#  define XSTAT __xstat
+#  define FXSTATAT __fxstatat
+# else
+#  define LXSTAT(V,f,sb) lstat (f,sb)
+#  define XSTAT(V,f,sb) stat (f,sb)
+#  define FXSTATAT(V,d,f,sb,m) fstatat (d, f, sb, m)
+# endif
+# define FTW_FUNC_T __ftw_func_t
+# define NFTW_FUNC_T __nftw_func_t
+#endif
+
+/* We define PATH_MAX if the system does not provide a definition.
+   This does not artificially limit any operation.  PATH_MAX is simply
+   used as a guesstimate for the expected maximal path length.
+   Buffers will be enlarged if necessary.  */
+#ifndef PATH_MAX
+# define PATH_MAX 1024
+#endif
+
+struct dir_data
+{
+  DIR *stream;
+  int streamfd;
+  char *content;
+};
+
+struct known_object
+{
+  dev_t dev;
+  INO_T ino;
+};
+
+struct ftw_data
+{
+  /* Array with pointers to open directory streams.  */
+  struct dir_data **dirstreams;
+  size_t actdir;
+  size_t maxdir;
+
+  /* Buffer containing name of currently processed object.  */
+  char *dirbuf;
+  size_t dirbufsize;
+
+  /* Passed as fourth argument to `nftw' callback.  The `base' member
+     tracks the content of the `dirbuf'.  */
+  struct FTW ftw;
+
+  /* Flags passed to `nftw' function.  0 for `ftw'.  */
+  int flags;
+
+  /* Conversion array for flag values.  It is the identity mapping for
+     `nftw' calls, otherwise it maps the values to those known by
+     `ftw'.  */
+  const int *cvt_arr;
+
+  /* Callback function.  We always use the `nftw' form.  */
+  NFTW_FUNC_T func;
+
+  /* Device of starting point.  Needed for FTW_MOUNT.  */
+  dev_t dev;
+
+  /* Data structure for keeping fingerprints of already processed
+     object.  This is needed when not using FTW_PHYS.  */
+  void *known_objects;
+};
+
+
+/* Internally we use the FTW_* constants used for `nftw'.  When invoked
+   as `ftw', map each flag to the subset of values used by `ftw'.  */
+static const int nftw_arr[] =
+{
+  FTW_F, FTW_D, FTW_DNR, FTW_NS, FTW_SL, FTW_DP, FTW_SLN
+};
+
+static const int ftw_arr[] =
+{
+  FTW_F, FTW_D, FTW_DNR, FTW_NS, FTW_F, FTW_D, FTW_NS
+};
+
+
+/* Forward declarations of local functions.  */
+static int ftw_dir (struct ftw_data *data, struct STAT *st,
+		    struct dir_data *old_dir) internal_function;
+
+
+static int
+object_compare (const void *p1, const void *p2)
+{
+  /* We don't need a sophisticated and useful comparison.  We are only
+     interested in equality.  However, we must be careful not to
+     accidentally compare `holes' in the structure.  */
+  const struct known_object *kp1 = p1, *kp2 = p2;
+  int cmp1;
+  cmp1 = (kp1->ino > kp2->ino) - (kp1->ino < kp2->ino);
+  if (cmp1 != 0)
+    return cmp1;
+  return (kp1->dev > kp2->dev) - (kp1->dev < kp2->dev);
+}
+
+
+static int
+add_object (struct ftw_data *data, struct STAT *st)
+{
+  struct known_object *newp = malloc (sizeof (struct known_object));
+  if (newp == NULL)
+    return -1;
+  newp->dev = st->st_dev;
+  newp->ino = st->st_ino;
+  return __tsearch (newp, &data->known_objects, object_compare) ? 0 : -1;
+}
+
+
+static inline int
+find_object (struct ftw_data *data, struct STAT *st)
+{
+  struct known_object obj;
+  obj.dev = st->st_dev;
+  obj.ino = st->st_ino;
+  return __tfind (&obj, &data->known_objects, object_compare) != NULL;
+}
+
+
+static inline int
+__attribute ((always_inline))
+open_dir_stream (int *dfdp, struct ftw_data *data, struct dir_data *dirp)
+{
+  int result = 0;
+
+  if (data->dirstreams[data->actdir] != NULL)
+    {
+      /* Oh, oh.  We must close this stream.  Get all remaining
+	 entries and store them as a list in the `content' member of
+	 the `struct dir_data' variable.  */
+      size_t bufsize = 1024;
+      char *buf = malloc (bufsize);
+
+      if (buf == NULL)
+	result = -1;
+      else
+	{
+	  DIR *st = data->dirstreams[data->actdir]->stream;
+	  struct dirent64 *d;
+	  size_t actsize = 0;
+
+	  while ((d = __readdir64 (st)) != NULL)
+	    {
+	      size_t this_len = NAMLEN (d);
+	      if (actsize + this_len + 2 >= bufsize)
+		{
+		  char *newp;
+		  bufsize += MAX (1024, 2 * this_len);
+		  newp = (char *) realloc (buf, bufsize);
+		  if (newp == NULL)
+		    {
+		      /* No more memory.  */
+		      int save_err = errno;
+		      free (buf);
+		      __set_errno (save_err);
+		      return -1;
+		    }
+		  buf = newp;
+		}
+
+	      *((char *) __mempcpy (buf + actsize, d->d_name, this_len))
+		= '\0';
+	      actsize += this_len + 1;
+	    }
+
+	  /* Terminate the list with an additional NUL byte.  */
+	  buf[actsize++] = '\0';
+
+	  /* Shrink the buffer to what we actually need.  */
+	  data->dirstreams[data->actdir]->content = realloc (buf, actsize);
+	  if (data->dirstreams[data->actdir]->content == NULL)
+	    {
+	      int save_err = errno;
+	      free (buf);
+	      __set_errno (save_err);
+	      result = -1;
+	    }
+	  else
+	    {
+	      __closedir (st);
+	      data->dirstreams[data->actdir]->stream = NULL;
+	      data->dirstreams[data->actdir]->streamfd = -1;
+	      data->dirstreams[data->actdir] = NULL;
+	    }
+	}
+    }
+
+  /* Open the new stream.  */
+  if (result == 0)
+    {
+      assert (data->dirstreams[data->actdir] == NULL);
+
+      if (dfdp != NULL && *dfdp != -1)
+	{
+	  int fd = openat64_not_cancel_3 (*dfdp, data->dirbuf + data->ftw.base,
+					  O_RDONLY | O_DIRECTORY | O_NDELAY);
+	  dirp->stream = NULL;
+	  if (fd != -1 && (dirp->stream = __fdopendir (fd)) == NULL)
+	    close_not_cancel_no_status (fd);
+	}
+      else
+	{
+	  const char *name;
+
+	  if (data->flags & FTW_CHDIR)
+	    {
+	      name = data->dirbuf + data->ftw.base;
+	      if (name[0] == '\0')
+		name = ".";
+	    }
+	  else
+	    name = data->dirbuf;
+
+	  dirp->stream = __opendir (name);
+	}
+
+      if (dirp->stream == NULL)
+	result = -1;
+      else
+	{
+	  dirp->streamfd = dirfd (dirp->stream);
+	  dirp->content = NULL;
+	  data->dirstreams[data->actdir] = dirp;
+
+	  if (++data->actdir == data->maxdir)
+	    data->actdir = 0;
+	}
+    }
+
+  return result;
+}
+
+
+static int
+internal_function
+process_entry (struct ftw_data *data, struct dir_data *dir, const char *name,
+	       size_t namlen, int d_type)
+{
+  struct STAT st;
+  int result = 0;
+  int flag = 0;
+  size_t new_buflen;
+
+  if (name[0] == '.' && (name[1] == '\0'
+			 || (name[1] == '.' && name[2] == '\0')))
+    /* Don't process the "." and ".." entries.  */
+    return 0;
+
+  new_buflen = data->ftw.base + namlen + 2;
+  if (data->dirbufsize < new_buflen)
+    {
+      /* Enlarge the buffer.  */
+      char *newp;
+
+      data->dirbufsize = 2 * new_buflen;
+      newp = (char *) realloc (data->dirbuf, data->dirbufsize);
+      if (newp == NULL)
+	return -1;
+      data->dirbuf = newp;
+    }
+
+  *((char *) __mempcpy (data->dirbuf + data->ftw.base, name, namlen)) = '\0';
+
+  int statres;
+  if (dir->streamfd != -1)
+    statres = FXSTATAT (_STAT_VER, dir->streamfd, name, &st,
+			(data->flags & FTW_PHYS) ? AT_SYMLINK_NOFOLLOW : 0);
+  else
+    {
+      if ((data->flags & FTW_CHDIR) == 0)
+	name = data->dirbuf;
+
+      statres = ((data->flags & FTW_PHYS)
+		 ? LXSTAT (_STAT_VER, name, &st)
+		 : XSTAT (_STAT_VER, name, &st));
+    }
+
+  if (statres < 0)
+    {
+      if (errno != EACCES && errno != ENOENT)
+	result = -1;
+      else if (data->flags & FTW_PHYS)
+	flag = FTW_NS;
+      else if (d_type == DT_LNK)
+	flag = FTW_SLN;
+      else
+	{
+	  if (dir->streamfd != -1)
+	    statres = FXSTATAT (_STAT_VER, dir->streamfd, name, &st,
+				AT_SYMLINK_NOFOLLOW);
+	  else
+	    statres = LXSTAT (_STAT_VER, name, &st);
+	  if (statres == 0 && S_ISLNK (st.st_mode))
+	    flag = FTW_SLN;
+	  else
+	    flag = FTW_NS;
+	}
+    }
+  else
+    {
+      if (S_ISDIR (st.st_mode))
+	flag = FTW_D;
+      else if (S_ISLNK (st.st_mode))
+	flag = FTW_SL;
+      else
+	flag = FTW_F;
+    }
+
+  if (result == 0
+      && (flag == FTW_NS
+	  || !(data->flags & FTW_MOUNT) || st.st_dev == data->dev))
+    {
+      if (flag == FTW_D)
+	{
+	  if ((data->flags & FTW_PHYS)
+	      || (!find_object (data, &st)
+		  /* Remember the object.  */
+		  && (result = add_object (data, &st)) == 0))
+	    result = ftw_dir (data, &st, dir);
+	}
+      else
+	result = (*data->func) (data->dirbuf, &st, data->cvt_arr[flag],
+				&data->ftw);
+    }
+
+  if ((data->flags & FTW_ACTIONRETVAL) && result == FTW_SKIP_SUBTREE)
+    result = 0;
+
+  return result;
+}
+
+
+static int
+__attribute ((noinline))
+internal_function
+ftw_dir (struct ftw_data *data, struct STAT *st, struct dir_data *old_dir)
+{
+  struct dir_data dir;
+  struct dirent64 *d;
+  int previous_base = data->ftw.base;
+  int result;
+  char *startp;
+
+  /* Open the stream for this directory.  This might require that
+     another stream has to be closed.  */
+  result = open_dir_stream (old_dir == NULL ? NULL : &old_dir->streamfd,
+			    data, &dir);
+  if (result != 0)
+    {
+      if (errno == EACCES)
+	/* We cannot read the directory.  Signal this with a special flag.  */
+	result = (*data->func) (data->dirbuf, st, FTW_DNR, &data->ftw);
+
+      return result;
+    }
+
+  /* First, report the directory (if not depth-first).  */
+  if (!(data->flags & FTW_DEPTH))
+    {
+      result = (*data->func) (data->dirbuf, st, FTW_D, &data->ftw);
+      if (result != 0)
+	{
+	  int save_err;
+fail:
+	  save_err = errno;
+	  __closedir (dir.stream);
+	  dir.streamfd = -1;
+	  __set_errno (save_err);
+
+	  if (data->actdir-- == 0)
+	    data->actdir = data->maxdir - 1;
+	  data->dirstreams[data->actdir] = NULL;
+	  return result;
+	}
+    }
+
+  /* If necessary, change to this directory.  */
+  if (data->flags & FTW_CHDIR)
+    {
+      if (__fchdir (dirfd (dir.stream)) < 0)
+	{
+	  result = -1;
+	  goto fail;
+	}
+    }
+
+  /* Next, update the `struct FTW' information.  */
+  ++data->ftw.level;
+  startp = __rawmemchr (data->dirbuf, '\0');
+  /* There always must be a directory name.  */
+  assert (startp != data->dirbuf);
+  if (startp[-1] != '/')
+    *startp++ = '/';
+  data->ftw.base = startp - data->dirbuf;
+
+  while (dir.stream != NULL && (d = __readdir64 (dir.stream)) != NULL)
+    {
+      int d_type = DT_UNKNOWN;
+#ifdef _DIRENT_HAVE_D_TYPE
+      d_type = d->d_type;
+#endif
+      result = process_entry (data, &dir, d->d_name, NAMLEN (d), d_type);
+      if (result != 0)
+	break;
+    }
+
+  if (dir.stream != NULL)
+    {
+      /* The stream is still open.  I.e., we did not need more
+	 descriptors.  Simply close the stream now.  */
+      int save_err = errno;
+
+      assert (dir.content == NULL);
+
+      __closedir (dir.stream);
+      dir.streamfd = -1;
+      __set_errno (save_err);
+
+      if (data->actdir-- == 0)
+	data->actdir = data->maxdir - 1;
+      data->dirstreams[data->actdir] = NULL;
+    }
+  else
+    {
+      int save_err;
+      char *runp = dir.content;
+
+      while (result == 0 && *runp != '\0')
+	{
+	  char *endp = strchr (runp, '\0');
+
+	  // XXX Should store the d_type values as well?!
+	  result = process_entry (data, &dir, runp, endp - runp, DT_UNKNOWN);
+
+	  runp = endp + 1;
+	}
+
+      save_err = errno;
+      free (dir.content);
+      __set_errno (save_err);
+    }
+
+  if ((data->flags & FTW_ACTIONRETVAL) && result == FTW_SKIP_SIBLINGS)
+    result = 0;
+
+  /* Prepare the return, revert the `struct FTW' information.  */
+  data->dirbuf[data->ftw.base - 1] = '\0';
+  --data->ftw.level;
+  data->ftw.base = previous_base;
+
+  /* Finally, if we process depth-first report the directory.  */
+  if (result == 0 && (data->flags & FTW_DEPTH))
+    result = (*data->func) (data->dirbuf, st, FTW_DP, &data->ftw);
+
+  if (old_dir
+      && (data->flags & FTW_CHDIR)
+      && (result == 0
+	  || ((data->flags & FTW_ACTIONRETVAL)
+	      && (result != -1 && result != FTW_STOP))))
+    {
+      /* Change back to the parent directory.  */
+      int done = 0;
+      if (old_dir->stream != NULL)
+	if (__fchdir (dirfd (old_dir->stream)) == 0)
+	  done = 1;
+
+      if (!done)
+	{
+	  if (data->ftw.base == 1)
+	    {
+	      if (__chdir ("/") < 0)
+		result = -1;
+	    }
+	  else
+	    if (__chdir ("..") < 0)
+	      result = -1;
+	}
+    }
+
+  return result;
+}
+
+
+static int
+__attribute ((noinline))
+internal_function
+ftw_startup (const char *dir, int is_nftw, void *func, int descriptors,
+	     int flags)
+{
+  struct ftw_data data;
+  struct STAT st;
+  int result = 0;
+  int save_err;
+  int cwdfd = -1;
+  char *cwd = NULL;
+  char *cp;
+
+  /* First make sure the parameters are reasonable.  */
+  if (dir[0] == '\0')
+    {
+      __set_errno (ENOENT);
+      return -1;
+    }
+
+  data.maxdir = descriptors < 1 ? 1 : descriptors;
+  data.actdir = 0;
+  data.dirstreams = (struct dir_data **) alloca (data.maxdir
+						 * sizeof (struct dir_data *));
+  memset (data.dirstreams, '\0', data.maxdir * sizeof (struct dir_data *));
+
+  /* PATH_MAX is always defined when we get here.  */
+  data.dirbufsize = MAX (2 * strlen (dir), PATH_MAX);
+  data.dirbuf = (char *) malloc (data.dirbufsize);
+  if (data.dirbuf == NULL)
+    return -1;
+  cp = __stpcpy (data.dirbuf, dir);
+  /* Strip trailing slashes.  */
+  while (cp > data.dirbuf + 1 && cp[-1] == '/')
+    --cp;
+  *cp = '\0';
+
+  data.ftw.level = 0;
+
+  /* Find basename.  */
+  while (cp > data.dirbuf && cp[-1] != '/')
+    --cp;
+  data.ftw.base = cp - data.dirbuf;
+
+  data.flags = flags;
+
+  /* This assignment might seem to be strange but it is what we want.
+     The trick is that the first three arguments to the `ftw' and
+     `nftw' callback functions are equal.  Therefore we can call in
+     every case the callback using the format of the `nftw' version
+     and get the correct result since the stack layout for a function
+     call in C allows this.  */
+  data.func = (NFTW_FUNC_T) func;
+
+  /* Since we internally use the complete set of FTW_* values we need
+     to reduce the value range before calling a `ftw' callback.  */
+  data.cvt_arr = is_nftw ? nftw_arr : ftw_arr;
+
+  /* No object known so far.  */
+  data.known_objects = NULL;
+
+  /* Now go to the directory containing the initial file/directory.  */
+  if (flags & FTW_CHDIR)
+    {
+      /* We have to be able to go back to the current working
+	 directory.  The best way to do this is to use a file
+	 descriptor.  */
+      cwdfd = __open (".", O_RDONLY | O_DIRECTORY);
+      if (cwdfd == -1)
+	{
+	  /* Try getting the directory name.  This can be needed if
+	     the current directory is executable but not readable.  */
+	  if (errno == EACCES)
+	    /* GNU extension ahead.  */
+	    cwd =  __getcwd (NULL, 0);
+
+	  if (cwd == NULL)
+	    goto out_fail;
+	}
+      else if (data.maxdir > 1)
+	/* Account for the file descriptor we use here.  */
+	--data.maxdir;
+
+      if (data.ftw.base > 0)
+	{
+	  /* Change to the directory the file is in.  In data.dirbuf
+	     we have a writable copy of the file name.  Just NUL
+	     terminate it for now and change the directory.  */
+	  if (data.ftw.base == 1)
+	    /* I.e., the file is in the root directory.  */
+	    result = __chdir ("/");
+	  else
+	    {
+	      char ch = data.dirbuf[data.ftw.base - 1];
+	      data.dirbuf[data.ftw.base - 1] = '\0';
+	      result = __chdir (data.dirbuf);
+	      data.dirbuf[data.ftw.base - 1] = ch;
+	    }
+	}
+    }
+
+  /* Get stat info for start directory.  */
+  if (result == 0)
+    {
+      const char *name;
+
+      if (data.flags & FTW_CHDIR)
+	{
+	  name = data.dirbuf + data.ftw.base;
+	  if (name[0] == '\0')
+	    name = ".";
+	}
+      else
+	name = data.dirbuf;
+
+      if (((flags & FTW_PHYS)
+	   ? LXSTAT (_STAT_VER, name, &st)
+	   : XSTAT (_STAT_VER, name, &st)) < 0)
+	{
+	  if (!(flags & FTW_PHYS)
+	      && errno == ENOENT
+	      && LXSTAT (_STAT_VER, name, &st) == 0
+	      && S_ISLNK (st.st_mode))
+	    result = (*data.func) (data.dirbuf, &st, data.cvt_arr[FTW_SLN],
+				   &data.ftw);
+	  else
+	    /* No need to call the callback since we cannot say anything
+	       about the object.  */
+	    result = -1;
+	}
+      else
+	{
+	  if (S_ISDIR (st.st_mode))
+	    {
+	      /* Remember the device of the initial directory in case
+		 FTW_MOUNT is given.  */
+	      data.dev = st.st_dev;
+
+	      /* We know this directory now.  */
+	      if (!(flags & FTW_PHYS))
+		result = add_object (&data, &st);
+
+	      if (result == 0)
+		result = ftw_dir (&data, &st, NULL);
+	    }
+	  else
+	    {
+	      int flag = S_ISLNK (st.st_mode) ? FTW_SL : FTW_F;
+
+	      result = (*data.func) (data.dirbuf, &st, data.cvt_arr[flag],
+				     &data.ftw);
+	    }
+	}
+
+      if ((flags & FTW_ACTIONRETVAL)
+	  && (result == FTW_SKIP_SUBTREE || result == FTW_SKIP_SIBLINGS))
+	result = 0;
+    }
+
+  /* Return to the start directory (if necessary).  */
+  if (cwdfd != -1)
+    {
+      int save_err = errno;
+      __fchdir (cwdfd);
+      close_not_cancel_no_status (cwdfd);
+      __set_errno (save_err);
+    }
+  else if (cwd != NULL)
+    {
+      int save_err = errno;
+      __chdir (cwd);
+      free (cwd);
+      __set_errno (save_err);
+    }
+
+  /* Free all memory.  */
+ out_fail:
+  save_err = errno;
+  __tdestroy (data.known_objects, free);
+  free (data.dirbuf);
+  __set_errno (save_err);
+
+  return result;
+}
+
+
+
+/* Entry points.  */
+
+int
+FTW_NAME (const char *path, FTW_FUNC_T func, int descriptors)
+{
+  return ftw_startup (path, 0, func, descriptors, 0);
+}
+
+#ifndef _LIBC
+int
+NFTW_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags)
+{
+  return ftw_startup (path, 1, func, descriptors, flags);
+}
+#else
+
+# include <shlib-compat.h>
+
+int NFTW_NEW_NAME (const char *, NFTW_FUNC_T, int, int);
+
+int
+NFTW_NEW_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags)
+{
+  if (flags
+      & ~(FTW_PHYS | FTW_MOUNT | FTW_CHDIR | FTW_DEPTH | FTW_ACTIONRETVAL))
+    {
+      __set_errno (EINVAL);
+      return -1;
+    }
+  return ftw_startup (path, 1, func, descriptors, flags);
+}
+
+versioned_symbol (libc, NFTW_NEW_NAME, NFTW_NAME, GLIBC_2_3_3);
+
+# if SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_3_3)
+
+/* Older nftw* version just ignored all unknown flags.  */
+
+int NFTW_OLD_NAME (const char *, NFTW_FUNC_T, int, int);
+
+int
+attribute_compat_text_section
+NFTW_OLD_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags)
+{
+  flags &= (FTW_PHYS | FTW_MOUNT | FTW_CHDIR | FTW_DEPTH);
+  return ftw_startup (path, 1, func, descriptors, flags);
+}
+
+compat_symbol (libc, NFTW_OLD_NAME, NFTW_NAME, GLIBC_2_1);
+# endif
+#endif