[PATCH 2/3] io: Consolidate ftw implementation

Adhemerval Zanella adhemerval.zanella@linaro.org
Fri Apr 17 13:24:56 GMT 2026


Remove wordsize-64 and arch-specific implementations, for ABIs when
off_t is the same as off64_t (__OFF_T_MATCHES_OFF64_T) the ftw64.c
will create the requires aliases.

The ftw.c implementation is moved to ftw-common.c to simplify
the __OFF_T_MATCHES_OFF64_T usage.
---
 io/ftw-common.c                               | 969 ++++++++++++++++++
 io/ftw.c                                      | 958 +----------------
 io/ftw64-time64.c                             |   2 +-
 io/ftw64.c                                    |  28 +-
 sysdeps/unix/sysv/linux/mips/mips64/n64/ftw.c |   1 -
 .../unix/sysv/linux/mips/mips64/n64/ftw64.c   |   1 -
 sysdeps/unix/sysv/linux/x86_64/x32/ftw.c      |   1 -
 sysdeps/unix/sysv/linux/x86_64/x32/ftw64.c    |   1 -
 sysdeps/wordsize-64/ftw.c                     |  16 -
 sysdeps/wordsize-64/ftw64.c                   |   1 -
 10 files changed, 1003 insertions(+), 975 deletions(-)
 create mode 100644 io/ftw-common.c
 delete mode 100644 sysdeps/unix/sysv/linux/mips/mips64/n64/ftw.c
 delete mode 100644 sysdeps/unix/sysv/linux/mips/mips64/n64/ftw64.c
 delete mode 100644 sysdeps/unix/sysv/linux/x86_64/x32/ftw.c
 delete mode 100644 sysdeps/unix/sysv/linux/x86_64/x32/ftw64.c
 delete mode 100644 sysdeps/wordsize-64/ftw.c
 delete mode 100644 sysdeps/wordsize-64/ftw64.c

diff --git a/io/ftw-common.c b/io/ftw-common.c
new file mode 100644
index 0000000000..07df0ab25a
--- /dev/null
+++ b/io/ftw-common.c
@@ -0,0 +1,969 @@
+/* File tree walker functions.
+   Copyright (C) 1996-2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   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
+   <https://www.gnu.org/licenses/>.  */
+
+
+#include <assert.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <ftw.h>
+#include <not-cancel.h>
+#include <search.h>
+#include <unistd.h>
+#include <sys/param.h>
+
+#define NAMLEN(dirent) _D_EXACT_NAMLEN (dirent)
+
+/* 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 STRUCT_STAT stat
+# define LSTAT __lstat
+# define STAT __stat
+# define FSTATAT __fstatat
+# 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;
+};
+
+/* Represents the execution state of a directory processing frame within the
+   iterative file tree walk loop.
+
+   Because the tree traversal is implemented iteratively using a custom stack
+   rather than standard recursion, this state machine tracks the progress
+   of each directory currently being visited.  */
+enum ftw_frame_state
+{
+  /* The initial state of a newly pushed directory frame.  Attempts to open
+     the directory stream.  If successful, transitions to
+     FTW_STATE_STREAM_LOOP.  */
+  FTW_STATE_INIT = 0,
+
+  /* Iterating over the directory entries directly from the open DIR stream
+     (using readdir).  If a subdirectory is encountered and needs to be
+     descended into, a new frame is added to the stack and execution pauses
+     here.  Transitions to FTW_STATE_CONTENT_LOOP if the stream was closed
+     and cached to free up file descriptors, or FTW_STATE_CLEANUP when
+     done.  */
+  FTW_STATE_STREAM_LOOP,
+
+  /* Iterating over directory entries from a cached memory buffer.  This state
+     is used as a fallback when the original DIR stream had to be closed
+     prematurely to prevent file descriptor exhaustion while descending into
+     deeply nested child directories.  Transitions to FTW_STATE_CLEANUP when
+     all cached entries are processed.  */
+  FTW_STATE_CONTENT_LOOP,
+
+  /* The final state, handles resource deallocation (closing remaining
+     streams, freeing cached content buffers), triggering post-traversal
+     callbacks (like FTW_DP for FTW_DEPTH walks), and restoring the
+     previous working directory if FTW_CHDIR was used.  */
+  FTW_STATE_CLEANUP
+};
+
+/* Keep track of visited directories.  */
+struct ftw_frame
+{
+  struct dir_data dir;
+  struct STRUCT_STAT st;
+  int previous_base;
+  char *runp;
+  enum ftw_frame_state state;
+};
+
+struct ftw_stack
+{
+  struct ftw_frame **stack;
+  size_t num_blocks;
+  ssize_t top;
+};
+
+typedef union
+{
+  NFTW_FUNC_T nftw_func;
+  FTW_FUNC_T ftw_func;
+} func_callback_t;
+
+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.  */
+  bool is_nftw;
+  func_callback_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;
+};
+#define CALL_FUNC(__ftw_data, __fp, __sb, __f, __ftw)                            \
+  ((__ftw_data)->is_nftw ? (__ftw_data)->func.nftw_func (__fp, __sb, __f, __ftw) \
+                         : (__ftw_data)->func.ftw_func (__fp, __sb, __f))
+
+static bool
+ftw_allocate (struct ftw_data *data, size_t newsize)
+{
+  void *newp = realloc (data->dirstreams, data->maxdir
+					  * sizeof (struct dir_data *)
+					  + newsize);
+  if (newp == NULL)
+    return false;
+  data->dirstreams = newp;
+  data->dirbufsize = newsize;
+  data->dirbuf = (char *) data->dirstreams
+		 + data->maxdir * sizeof (struct dir_data *);
+  return true;
+}
+
+/* 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
+};
+
+
+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 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 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
+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.  */
+		      free (buf);
+		      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.  */
+	  void *content = realloc (buf, actsize);
+	  data->dirstreams[data->actdir]->content = content;
+	  if (content == NULL)
+	    {
+	      free (buf);
+	      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_nocancel (*dfdp, data->dirbuf + data->ftw.base,
+					O_RDONLY | O_DIRECTORY | O_NDELAY);
+	  dirp->stream = NULL;
+	  if (fd != -1 && (dirp->stream = __fdopendir (fd)) == NULL)
+	    __close_nocancel_nostatus (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
+process_entry (struct ftw_data *data, struct dir_data *dir, const char *name,
+	       size_t namlen, struct STRUCT_STAT *out_st, bool *descend)
+{
+  struct STRUCT_STAT st;
+  int result = 0;
+  int flag = 0;
+  size_t new_buflen;
+
+  *descend = false;
+
+  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
+      && !ftw_allocate (data, 2 * new_buflen))
+    return -1;
+
+  *((char *) __mempcpy (data->dirbuf + data->ftw.base, name, namlen)) = '\0';
+
+  int statres;
+  if (dir->streamfd != -1)
+    statres = FSTATAT (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)
+		 ? LSTAT (name, &st)
+		 : STAT (name, &st));
+    }
+
+  if (statres < 0)
+    {
+      if (errno != EACCES && errno != ENOENT)
+	result = -1;
+      else if (data->flags & FTW_PHYS)
+	flag = FTW_NS;
+      else
+	{
+	  /* Old code left ST undefined for dangling DT_LNK without
+	     FTW_PHYS set; a clarification at the POSIX level suggests
+	     it should contain information about the link (ala lstat).
+	     We do our best to fill in what data we can.  */
+	  if (dir->streamfd != -1)
+	    statres = FSTATAT (dir->streamfd, name, &st,
+			       AT_SYMLINK_NOFOLLOW);
+	  else
+	    statres = LSTAT (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))
+	    {
+               *out_st = st;
+               *descend = true;
+	    }
+	}
+      else
+	result = CALL_FUNC (data, data->dirbuf, &st, data->cvt_arr[flag],
+			    &data->ftw);
+    }
+
+  if ((data->flags & FTW_ACTIONRETVAL) && result == FTW_SKIP_SUBTREE)
+    result = 0;
+
+  return result;
+}
+
+
+/* The ftw_frame are kept as chunked array to minimize the reallocation cost
+   when the stack grows (since it contains STRUCT_STAT and extra metadata).
+   New chunks of ftw_framw are allocated and only freed when ftw returns.  */
+enum
+{
+  FTW_STACK_CHUNK_BLOCKS  = 1,  /* Number of initial allocated chunks.  */
+  FTW_STACK_CHUNK_SIZE    = 32  /* Number of stack frames allocated per
+				   chunk.  */
+};
+
+static inline struct ftw_frame *
+frame_stack_get (struct ftw_stack *ftwst, int adj)
+{
+  return &ftwst->stack[(ftwst->top + adj) / FTW_STACK_CHUNK_SIZE]
+    [(ftwst->top + adj) % FTW_STACK_CHUNK_SIZE];
+}
+
+static inline void
+frame_stack_reset_top (struct ftw_stack *fwtst, const struct STRUCT_STAT *st)
+{
+  struct ftw_frame *frame = frame_stack_get (fwtst, 0);
+  frame->st = *st;
+  frame->state = FTW_STATE_INIT;
+  frame->dir.stream = NULL;
+  frame->dir.content = NULL;
+  frame->dir.streamfd = -1;
+}
+
+static bool
+frame_stack_init (struct ftw_stack *ftwst, const struct STRUCT_STAT *st)
+{
+  ftwst->num_blocks = FTW_STACK_CHUNK_BLOCKS;
+  ftwst->stack = malloc (FTW_STACK_CHUNK_BLOCKS * sizeof (*ftwst->stack));
+  if (ftwst->stack == NULL)
+    return false;
+
+  ftwst->stack[0] = malloc (FTW_STACK_CHUNK_SIZE * sizeof (struct ftw_frame));
+  if (ftwst->stack[0] == NULL)
+    {
+      free (ftwst->stack);
+      return false;
+    }
+
+  ftwst->top = 0;
+  frame_stack_reset_top (ftwst, st);
+  return true;
+}
+
+static void
+frame_stack_free (struct ftw_stack *ftwst)
+{
+  for (size_t i = 0; i < ftwst->num_blocks; i++)
+    free (ftwst->stack[i]);
+  free (ftwst->stack);
+}
+
+static bool
+frame_stack_add (struct ftw_stack *ftwst, const struct STRUCT_STAT *st)
+{
+  if (ftwst->top + 1 >= ftwst->num_blocks * FTW_STACK_CHUNK_SIZE)
+    {
+      size_t new_blocks = ftwst->num_blocks + 1;
+      struct ftw_frame **new_stack = realloc (
+	  ftwst->stack, new_blocks * sizeof (*ftwst->stack));
+
+      if (new_stack == NULL)
+	return false;
+      ftwst->stack = new_stack;
+      ftwst->stack[ftwst->num_blocks] = malloc (
+	  FTW_STACK_CHUNK_SIZE * sizeof (struct ftw_frame));
+      if (ftwst->stack[ftwst->num_blocks] == NULL)
+	return false;
+      ftwst->num_blocks = new_blocks;
+    }
+  ftwst->top++;
+  frame_stack_reset_top (ftwst, st);
+  return true;
+}
+
+static void
+frame_closedir (struct ftw_data *data, struct ftw_frame *frame)
+{
+  int save_err = errno;
+  assert (frame->dir.content == NULL);
+  __closedir (frame->dir.stream);
+  frame->dir.streamfd = -1;
+  __set_errno (save_err);
+  if (data->actdir-- == 0)
+    data->actdir = data->maxdir - 1;
+  data->dirstreams[data->actdir] = NULL;
+  frame->dir.stream = NULL;
+}
+
+static int
+ftw_dir (struct ftw_data *data, const struct STRUCT_STAT *st)
+{
+  struct ftw_stack ftwst;
+  if (!frame_stack_init (&ftwst, st))
+    return -1;
+
+  int result = 0;
+
+  while (ftwst.top >= 0)
+    {
+      struct ftw_frame *frame = frame_stack_get (&ftwst, 0);
+      struct dir_data *old_dir = (ftwst.top > 0)
+	? &frame_stack_get (&ftwst, -1)->dir : NULL;
+
+      if (frame->state == FTW_STATE_INIT)
+	{
+	  frame->previous_base = data->ftw.base;
+	  result = open_dir_stream (
+	      old_dir == NULL ? NULL : &old_dir->streamfd, data, &frame->dir);
+	  if (result != 0)
+	    {
+	      if (errno == EACCES)
+		result = CALL_FUNC (data, data->dirbuf, &frame->st, FTW_DNR,
+				    &data->ftw);
+	      ftwst.top--;
+	      /* Intercept FTW_SKIP_SUBTREE when popping frame */
+	      if (ftwst.top >= 0 && (data->flags & FTW_ACTIONRETVAL)
+		  && result == FTW_SKIP_SUBTREE)
+		result = 0;
+	      continue;
+	    }
+
+	  if (!(data->flags & FTW_DEPTH))
+	    {
+	      result = CALL_FUNC (data, data->dirbuf, &frame->st, FTW_D,
+				  &data->ftw);
+	      if (result != 0)
+		goto state0_fail;
+	    }
+
+	  if (data->flags & FTW_CHDIR)
+	    {
+	      if (__fchdir (__dirfd (frame->dir.stream)) < 0)
+		{
+		  result = -1;
+		state0_fail:
+		  frame_closedir (data, frame);
+		  ftwst.top--;
+		  /* Intercept FTW_SKIP_SUBTREE when popping frame.  */
+		  if (ftwst.top >= 0 && (data->flags & FTW_ACTIONRETVAL)
+		      && result == FTW_SKIP_SUBTREE)
+		    result = 0;
+		  continue;
+		}
+	    }
+
+	  ++data->ftw.level;
+	  char *startp = strchr (data->dirbuf, '\0');
+	  assert (startp != data->dirbuf);
+	  if (startp[-1] != '/')
+	    *startp++ = '/';
+	  data->ftw.base = startp - data->dirbuf;
+
+	  frame->state = FTW_STATE_STREAM_LOOP;
+	  frame->runp = frame->dir.content;
+	}
+      else if (frame->state == FTW_STATE_STREAM_LOOP)
+	{
+	  if (result != 0)
+	    {
+	      frame->state = FTW_STATE_CLEANUP;
+	      continue;
+	    }
+
+	  if (frame->dir.stream == NULL)
+	    {
+	      frame->state = FTW_STATE_CONTENT_LOOP;
+	      frame->runp = frame->dir.content;
+	      continue;
+	    }
+
+	  struct dirent64 *d = __readdir64 (frame->dir.stream);
+	  if (d != NULL)
+	    {
+	      struct STRUCT_STAT child_st;
+	      bool descend = false;
+	      result = process_entry (data, &frame->dir, d->d_name, NAMLEN (d),
+				      &child_st, &descend);
+
+	      if (result == 0 && descend)
+		{
+		  if (!frame_stack_add (&ftwst, &child_st))
+		    {
+		      result = -1;
+		      frame->state = FTW_STATE_CLEANUP;
+		    }
+		  continue;
+		}
+	      else if (result != 0)
+		{
+		  frame->state = FTW_STATE_CLEANUP;
+		  continue;
+		}
+	    }
+	  else
+	    frame->state = FTW_STATE_CLEANUP;
+	}
+      else if (frame->state == FTW_STATE_CONTENT_LOOP)
+	{
+	  /* Check if we are safely positioned to process the starting path.
+	     The 'result' variable here comes from one of two places:
+
+	     1. Initialization: defaults to 0 at the top of ftw_startup.  If
+	        the FTW_CHDIR flag was NOT passed, it remains 0, meaning we
+		are good to go.
+
+	     2. Directory Change: If FTW_CHDIR WAS passed, 'result' holds the
+	        return value of the preceding __chdir() call (either moving
+		to "/" or the parsed base directory).
+
+	     If 'result' is 0, the setup succeeded (or wasn't needed) and we
+	     can safely stat the initial object.  Othewise, the chdir failed,
+	     so we skip processing and fall through to the cleanup phase.  */
+	  if (result != 0)
+	    {
+	      frame->state = FTW_STATE_CLEANUP;
+	      continue;
+	    }
+
+	  if (frame->runp != NULL && *frame->runp != '\0')
+	    {
+	      char *endp = strchr (frame->runp, '\0');
+	      struct STRUCT_STAT child_st;
+	      bool descend = false;
+
+	      result = process_entry (data, &frame->dir, frame->runp,
+				      endp - frame->runp, &child_st,
+				      &descend);
+	      frame->runp = endp + 1;
+
+	      if (result == 0 && descend)
+		{
+		  if (!frame_stack_add (&ftwst, &child_st))
+		    {
+		      result = -1;
+		      frame->state = FTW_STATE_CLEANUP;
+		    }
+		  continue;
+		}
+	      else if (result != 0)
+		{
+		  frame->state = FTW_STATE_CLEANUP;
+		  continue;
+		}
+	    }
+	  else
+	    frame->state = FTW_STATE_CLEANUP;
+	}
+      else if (frame->state == FTW_STATE_CLEANUP)
+	{
+	  if (frame->dir.stream != NULL)
+	    frame_closedir (data, frame);
+	  else if (frame->dir.content != NULL)
+	    {
+	      free (frame->dir.content);
+	      frame->dir.content = NULL;
+	    }
+
+	  if ((data->flags & FTW_ACTIONRETVAL) && result == FTW_SKIP_SIBLINGS)
+	    result = 0;
+
+	  data->dirbuf[data->ftw.base - 1] = '\0';
+	  --data->ftw.level;
+	  data->ftw.base = frame->previous_base;
+
+	  if (result == 0 && (data->flags & FTW_DEPTH))
+	    result
+		= CALL_FUNC (data, data->dirbuf, &frame->st, FTW_DP,
+			     &data->ftw);
+
+	  if (old_dir != NULL && (data->flags & FTW_CHDIR)
+	      && (result == 0
+		  || ((data->flags & FTW_ACTIONRETVAL)
+		      && (result != -1 && result != FTW_STOP))))
+	    {
+	      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;
+		}
+	    }
+
+	  ftwst.top--;
+	  /* Intercept FTW_SKIP_SUBTREE when popping frame.  */
+	  if (ftwst.top >= 0 && (data->flags & FTW_ACTIONRETVAL)
+	      && result == FTW_SKIP_SUBTREE)
+	    result = 0;
+	}
+    }
+
+  frame_stack_free (&ftwst);
+
+  return result;
+}
+
+
+static int
+ftw_startup (const char *dir, bool is_nftw, func_callback_t func,
+	     int descriptors, int flags)
+{
+  struct ftw_data data = { .dirstreams = NULL };
+  struct STRUCT_STAT st;
+  int result = 0;
+  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;
+  /* PATH_MAX is always defined when we get here.  */
+  if (!ftw_allocate (&data, MAX (2 * strlen (dir), PATH_MAX)))
+    return -1;
+  memset (data.dirstreams, '\0', data.maxdir * sizeof (struct dir_data *));
+  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;
+
+  data.is_nftw = is_nftw;
+  data.func = 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)
+	   ? LSTAT (name, &st)
+	   : STAT (name, &st)) < 0)
+	{
+	  if (!(flags & FTW_PHYS)
+	      && errno == ENOENT
+	      && LSTAT (name, &st) == 0
+	      && S_ISLNK (st.st_mode))
+	    result = CALL_FUNC (&data, 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);
+	    }
+	  else
+	    {
+	      int flag = S_ISLNK (st.st_mode) ? FTW_SL : FTW_F;
+
+	      result = CALL_FUNC (&data, 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_nocancel_nostatus (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:
+  __tdestroy (data.known_objects, free);
+  free (data.dirstreams);
+
+  return result;
+}
+
+
+
+/* Entry points.  */
+
+int
+FTW_NAME (const char *path, FTW_FUNC_T func, int descriptors)
+{
+  return ftw_startup (path, false, (func_callback_t) { .ftw_func = func },
+		      descriptors, 0);
+}
+
+#ifndef NFTW_OLD_NAME
+int
+NFTW_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags)
+{
+  return ftw_startup (path, true, (func_callback_t) { .nftw_func = 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, true, (func_callback_t) { .nftw_func = func },
+		      descriptors, flags);
+}
+
+# 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, true, (func_callback_t) { .nftw_func = func },
+		      descriptors, flags);
+}
+
+# endif
+#endif /* NFTW_OLD_NAME  */
diff --git a/io/ftw.c b/io/ftw.c
index 726c430eaf..ed0eeb3904 100644
--- a/io/ftw.c
+++ b/io/ftw.c
@@ -1,5 +1,5 @@
-/* File tree walker functions.
-   Copyright (C) 1996-2026 Free Software Foundation, Inc.
+/* File tree traversal functions LFS version.
+   Copyright (C) 2026 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
 
    The GNU C Library is free software; you can redistribute it and/or
@@ -16,956 +16,14 @@
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
+#include <sys/types.h>
 
-#include <assert.h>
-#include <dirent.h>
-#include <fcntl.h>
-#include <ftw.h>
-#include <not-cancel.h>
-#include <search.h>
-#include <unistd.h>
-#include <sys/param.h>
+#ifndef __OFF_T_MATCHES_OFF64_T
+# include "ftw-common.c"
 
-#define NAMLEN(dirent) _D_EXACT_NAMLEN (dirent)
-
-/* 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 STRUCT_STAT stat
-# define LSTAT __lstat
-# define STAT __stat
-# define FSTATAT __fstatat
-# define FTW_FUNC_T __ftw_func_t
-# define NFTW_FUNC_T __nftw_func_t
+versioned_symbol (libc, __new_nftw, nftw, GLIBC_2_3_3);
+#if SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_3_3)
+compat_symbol (libc, __old_nftw, nftw, GLIBC_2_1);
 #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;
-};
-
-/* Represents the execution state of a directory processing frame within the
-   iterative file tree walk loop.
-
-   Because the tree traversal is implemented iteratively using a custom stack
-   rather than standard recursion, this state machine tracks the progress
-   of each directory currently being visited.  */
-enum ftw_frame_state
-{
-  /* The initial state of a newly pushed directory frame.  Attempts to open
-     the directory stream.  If successful, transitions to
-     FTW_STATE_STREAM_LOOP.  */
-  FTW_STATE_INIT = 0,
-
-  /* Iterating over the directory entries directly from the open DIR stream
-     (using readdir).  If a subdirectory is encountered and needs to be
-     descended into, a new frame is added to the stack and execution pauses
-     here.  Transitions to FTW_STATE_CONTENT_LOOP if the stream was closed
-     and cached to free up file descriptors, or FTW_STATE_CLEANUP when
-     done.  */
-  FTW_STATE_STREAM_LOOP,
-
-  /* Iterating over directory entries from a cached memory buffer.  This state
-     is used as a fallback when the original DIR stream had to be closed
-     prematurely to prevent file descriptor exhaustion while descending into
-     deeply nested child directories.  Transitions to FTW_STATE_CLEANUP when
-     all cached entries are processed.  */
-  FTW_STATE_CONTENT_LOOP,
-
-  /* The final state, handles resource deallocation (closing remaining
-     streams, freeing cached content buffers), triggering post-traversal
-     callbacks (like FTW_DP for FTW_DEPTH walks), and restoring the
-     previous working directory if FTW_CHDIR was used.  */
-  FTW_STATE_CLEANUP
-};
-
-/* Keep track of visited directories.  */
-struct ftw_frame
-{
-  struct dir_data dir;
-  struct STRUCT_STAT st;
-  int previous_base;
-  char *runp;
-  enum ftw_frame_state state;
-};
-
-struct ftw_stack
-{
-  struct ftw_frame **stack;
-  size_t num_blocks;
-  ssize_t top;
-};
-
-typedef union
-{
-  NFTW_FUNC_T nftw_func;
-  FTW_FUNC_T ftw_func;
-} func_callback_t;
-
-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.  */
-  bool is_nftw;
-  func_callback_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;
-};
-#define CALL_FUNC(__ftw_data, __fp, __sb, __f, __ftw)                            \
-  ((__ftw_data)->is_nftw ? (__ftw_data)->func.nftw_func (__fp, __sb, __f, __ftw) \
-                         : (__ftw_data)->func.ftw_func (__fp, __sb, __f))
-
-static bool
-ftw_allocate (struct ftw_data *data, size_t newsize)
-{
-  void *newp = realloc (data->dirstreams, data->maxdir
-					  * sizeof (struct dir_data *)
-					  + newsize);
-  if (newp == NULL)
-    return false;
-  data->dirstreams = newp;
-  data->dirbufsize = newsize;
-  data->dirbuf = (char *) data->dirstreams
-		 + data->maxdir * sizeof (struct dir_data *);
-  return true;
-}
-
-/* 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
-};
-
-
-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 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 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
-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.  */
-		      free (buf);
-		      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.  */
-	  void *content = realloc (buf, actsize);
-	  data->dirstreams[data->actdir]->content = content;
-	  if (content == NULL)
-	    {
-	      free (buf);
-	      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_nocancel (*dfdp, data->dirbuf + data->ftw.base,
-					O_RDONLY | O_DIRECTORY | O_NDELAY);
-	  dirp->stream = NULL;
-	  if (fd != -1 && (dirp->stream = __fdopendir (fd)) == NULL)
-	    __close_nocancel_nostatus (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
-process_entry (struct ftw_data *data, struct dir_data *dir, const char *name,
-	       size_t namlen, struct STRUCT_STAT *out_st, bool *descend)
-{
-  struct STRUCT_STAT st;
-  int result = 0;
-  int flag = 0;
-  size_t new_buflen;
-
-  *descend = false;
-
-  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
-      && !ftw_allocate (data, 2 * new_buflen))
-    return -1;
-
-  *((char *) __mempcpy (data->dirbuf + data->ftw.base, name, namlen)) = '\0';
-
-  int statres;
-  if (dir->streamfd != -1)
-    statres = FSTATAT (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)
-		 ? LSTAT (name, &st)
-		 : STAT (name, &st));
-    }
-
-  if (statres < 0)
-    {
-      if (errno != EACCES && errno != ENOENT)
-	result = -1;
-      else if (data->flags & FTW_PHYS)
-	flag = FTW_NS;
-      else
-	{
-	  /* Old code left ST undefined for dangling DT_LNK without
-	     FTW_PHYS set; a clarification at the POSIX level suggests
-	     it should contain information about the link (ala lstat).
-	     We do our best to fill in what data we can.  */
-	  if (dir->streamfd != -1)
-	    statres = FSTATAT (dir->streamfd, name, &st,
-			       AT_SYMLINK_NOFOLLOW);
-	  else
-	    statres = LSTAT (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))
-	    {
-               *out_st = st;
-               *descend = true;
-	    }
-	}
-      else
-	result = CALL_FUNC (data, data->dirbuf, &st, data->cvt_arr[flag],
-			    &data->ftw);
-    }
-
-  if ((data->flags & FTW_ACTIONRETVAL) && result == FTW_SKIP_SUBTREE)
-    result = 0;
-
-  return result;
-}
-
-
-/* The ftw_frame are kept as chunked array to minimize the reallocation cost
-   when the stack grows (since it contains STRUCT_STAT and extra metadata).
-   New chunks of ftw_framw are allocated and only freed when ftw returns.  */
-enum
-{
-  FTW_STACK_CHUNK_BLOCKS  = 1,  /* Number of initial allocated chunks.  */
-  FTW_STACK_CHUNK_SIZE    = 32  /* Number of stack frames allocated per
-				   chunk.  */
-};
-
-static inline struct ftw_frame *
-frame_stack_get (struct ftw_stack *ftwst, int adj)
-{
-  return &ftwst->stack[(ftwst->top + adj) / FTW_STACK_CHUNK_SIZE]
-    [(ftwst->top + adj) % FTW_STACK_CHUNK_SIZE];
-}
-
-static inline void
-frame_stack_reset_top (struct ftw_stack *fwtst, const struct STRUCT_STAT *st)
-{
-  struct ftw_frame *frame = frame_stack_get (fwtst, 0);
-  frame->st = *st;
-  frame->state = FTW_STATE_INIT;
-  frame->dir.stream = NULL;
-  frame->dir.content = NULL;
-  frame->dir.streamfd = -1;
-}
-
-static bool
-frame_stack_init (struct ftw_stack *ftwst, const struct STRUCT_STAT *st)
-{
-  ftwst->num_blocks = FTW_STACK_CHUNK_BLOCKS;
-  ftwst->stack = malloc (FTW_STACK_CHUNK_BLOCKS * sizeof (*ftwst->stack));
-  if (ftwst->stack == NULL)
-    return false;
-
-  ftwst->stack[0] = malloc (FTW_STACK_CHUNK_SIZE * sizeof (struct ftw_frame));
-  if (ftwst->stack[0] == NULL)
-    {
-      free (ftwst->stack);
-      return false;
-    }
-
-  ftwst->top = 0;
-  frame_stack_reset_top (ftwst, st);
-  return true;
-}
-
-static void
-frame_stack_free (struct ftw_stack *ftwst)
-{
-  for (size_t i = 0; i < ftwst->num_blocks; i++)
-    free (ftwst->stack[i]);
-  free (ftwst->stack);
-}
-
-static bool
-frame_stack_add (struct ftw_stack *ftwst, const struct STRUCT_STAT *st)
-{
-  if (ftwst->top + 1 >= ftwst->num_blocks * FTW_STACK_CHUNK_SIZE)
-    {
-      size_t new_blocks = ftwst->num_blocks + 1;
-      struct ftw_frame **new_stack = realloc (
-	  ftwst->stack, new_blocks * sizeof (*ftwst->stack));
-
-      if (new_stack == NULL)
-	return false;
-      ftwst->stack = new_stack;
-      ftwst->stack[ftwst->num_blocks] = malloc (
-	  FTW_STACK_CHUNK_SIZE * sizeof (struct ftw_frame));
-      if (ftwst->stack[ftwst->num_blocks] == NULL)
-	return false;
-      ftwst->num_blocks = new_blocks;
-    }
-  ftwst->top++;
-  frame_stack_reset_top (ftwst, st);
-  return true;
-}
-
-static void
-frame_closedir (struct ftw_data *data, struct ftw_frame *frame)
-{
-  int save_err = errno;
-  assert (frame->dir.content == NULL);
-  __closedir (frame->dir.stream);
-  frame->dir.streamfd = -1;
-  __set_errno (save_err);
-  if (data->actdir-- == 0)
-    data->actdir = data->maxdir - 1;
-  data->dirstreams[data->actdir] = NULL;
-  frame->dir.stream = NULL;
-}
-
-static int
-ftw_dir (struct ftw_data *data, const struct STRUCT_STAT *st)
-{
-  struct ftw_stack ftwst;
-  if (!frame_stack_init (&ftwst, st))
-    return -1;
-
-  int result = 0;
-
-  while (ftwst.top >= 0)
-    {
-      struct ftw_frame *frame = frame_stack_get (&ftwst, 0);
-      struct dir_data *old_dir = (ftwst.top > 0)
-	? &frame_stack_get (&ftwst, -1)->dir : NULL;
-
-      if (frame->state == FTW_STATE_INIT)
-	{
-	  frame->previous_base = data->ftw.base;
-	  result = open_dir_stream (
-	      old_dir == NULL ? NULL : &old_dir->streamfd, data, &frame->dir);
-	  if (result != 0)
-	    {
-	      if (errno == EACCES)
-		result = CALL_FUNC (data, data->dirbuf, &frame->st, FTW_DNR,
-				    &data->ftw);
-	      ftwst.top--;
-	      /* Intercept FTW_SKIP_SUBTREE when popping frame */
-	      if (ftwst.top >= 0 && (data->flags & FTW_ACTIONRETVAL)
-		  && result == FTW_SKIP_SUBTREE)
-		result = 0;
-	      continue;
-	    }
-
-	  if (!(data->flags & FTW_DEPTH))
-	    {
-	      result = CALL_FUNC (data, data->dirbuf, &frame->st, FTW_D,
-				  &data->ftw);
-	      if (result != 0)
-		goto state0_fail;
-	    }
-
-	  if (data->flags & FTW_CHDIR)
-	    {
-	      if (__fchdir (__dirfd (frame->dir.stream)) < 0)
-		{
-		  result = -1;
-		state0_fail:
-		  frame_closedir (data, frame);
-		  ftwst.top--;
-		  /* Intercept FTW_SKIP_SUBTREE when popping frame.  */
-		  if (ftwst.top >= 0 && (data->flags & FTW_ACTIONRETVAL)
-		      && result == FTW_SKIP_SUBTREE)
-		    result = 0;
-		  continue;
-		}
-	    }
-
-	  ++data->ftw.level;
-	  char *startp = strchr (data->dirbuf, '\0');
-	  assert (startp != data->dirbuf);
-	  if (startp[-1] != '/')
-	    *startp++ = '/';
-	  data->ftw.base = startp - data->dirbuf;
-
-	  frame->state = FTW_STATE_STREAM_LOOP;
-	  frame->runp = frame->dir.content;
-	}
-      else if (frame->state == FTW_STATE_STREAM_LOOP)
-	{
-	  if (result != 0)
-	    {
-	      frame->state = FTW_STATE_CLEANUP;
-	      continue;
-	    }
-
-	  if (frame->dir.stream == NULL)
-	    {
-	      frame->state = FTW_STATE_CONTENT_LOOP;
-	      frame->runp = frame->dir.content;
-	      continue;
-	    }
-
-	  struct dirent64 *d = __readdir64 (frame->dir.stream);
-	  if (d != NULL)
-	    {
-	      struct STRUCT_STAT child_st;
-	      bool descend = false;
-	      result = process_entry (data, &frame->dir, d->d_name, NAMLEN (d),
-				      &child_st, &descend);
-
-	      if (result == 0 && descend)
-		{
-		  if (!frame_stack_add (&ftwst, &child_st))
-		    {
-		      result = -1;
-		      frame->state = FTW_STATE_CLEANUP;
-		    }
-		  continue;
-		}
-	      else if (result != 0)
-		{
-		  frame->state = FTW_STATE_CLEANUP;
-		  continue;
-		}
-	    }
-	  else
-	    frame->state = FTW_STATE_CLEANUP;
-	}
-      else if (frame->state == FTW_STATE_CONTENT_LOOP)
-	{
-	  /* Check if we are safely positioned to process the starting path.
-	     The 'result' variable here comes from one of two places:
-
-	     1. Initialization: defaults to 0 at the top of ftw_startup.  If
-	        the FTW_CHDIR flag was NOT passed, it remains 0, meaning we
-		are good to go.
-
-	     2. Directory Change: If FTW_CHDIR WAS passed, 'result' holds the
-	        return value of the preceding __chdir() call (either moving
-		to "/" or the parsed base directory).
-
-	     If 'result' is 0, the setup succeeded (or wasn't needed) and we
-	     can safely stat the initial object.  Othewise, the chdir failed,
-	     so we skip processing and fall through to the cleanup phase.  */
-	  if (result != 0)
-	    {
-	      frame->state = FTW_STATE_CLEANUP;
-	      continue;
-	    }
-
-	  if (frame->runp != NULL && *frame->runp != '\0')
-	    {
-	      char *endp = strchr (frame->runp, '\0');
-	      struct STRUCT_STAT child_st;
-	      bool descend = false;
-
-	      result = process_entry (data, &frame->dir, frame->runp,
-				      endp - frame->runp, &child_st,
-				      &descend);
-	      frame->runp = endp + 1;
-
-	      if (result == 0 && descend)
-		{
-		  if (!frame_stack_add (&ftwst, &child_st))
-		    {
-		      result = -1;
-		      frame->state = FTW_STATE_CLEANUP;
-		    }
-		  continue;
-		}
-	      else if (result != 0)
-		{
-		  frame->state = FTW_STATE_CLEANUP;
-		  continue;
-		}
-	    }
-	  else
-	    frame->state = FTW_STATE_CLEANUP;
-	}
-      else if (frame->state == FTW_STATE_CLEANUP)
-	{
-	  if (frame->dir.stream != NULL)
-	    frame_closedir (data, frame);
-	  else if (frame->dir.content != NULL)
-	    {
-	      free (frame->dir.content);
-	      frame->dir.content = NULL;
-	    }
-
-	  if ((data->flags & FTW_ACTIONRETVAL) && result == FTW_SKIP_SIBLINGS)
-	    result = 0;
-
-	  data->dirbuf[data->ftw.base - 1] = '\0';
-	  --data->ftw.level;
-	  data->ftw.base = frame->previous_base;
-
-	  if (result == 0 && (data->flags & FTW_DEPTH))
-	    result
-		= CALL_FUNC (data, data->dirbuf, &frame->st, FTW_DP,
-			     &data->ftw);
-
-	  if (old_dir != NULL && (data->flags & FTW_CHDIR)
-	      && (result == 0
-		  || ((data->flags & FTW_ACTIONRETVAL)
-		      && (result != -1 && result != FTW_STOP))))
-	    {
-	      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;
-		}
-	    }
-
-	  ftwst.top--;
-	  /* Intercept FTW_SKIP_SUBTREE when popping frame.  */
-	  if (ftwst.top >= 0 && (data->flags & FTW_ACTIONRETVAL)
-	      && result == FTW_SKIP_SUBTREE)
-	    result = 0;
-	}
-    }
-
-  frame_stack_free (&ftwst);
-
-  return result;
-}
-
-
-static int
-ftw_startup (const char *dir, bool is_nftw, func_callback_t func,
-	     int descriptors, int flags)
-{
-  struct ftw_data data = { .dirstreams = NULL };
-  struct STRUCT_STAT st;
-  int result = 0;
-  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;
-  /* PATH_MAX is always defined when we get here.  */
-  if (!ftw_allocate (&data, MAX (2 * strlen (dir), PATH_MAX)))
-    return -1;
-  memset (data.dirstreams, '\0', data.maxdir * sizeof (struct dir_data *));
-  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;
-
-  data.is_nftw = is_nftw;
-  data.func = 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)
-	   ? LSTAT (name, &st)
-	   : STAT (name, &st)) < 0)
-	{
-	  if (!(flags & FTW_PHYS)
-	      && errno == ENOENT
-	      && LSTAT (name, &st) == 0
-	      && S_ISLNK (st.st_mode))
-	    result = CALL_FUNC (&data, 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);
-	    }
-	  else
-	    {
-	      int flag = S_ISLNK (st.st_mode) ? FTW_SL : FTW_F;
-
-	      result = CALL_FUNC (&data, 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_nocancel_nostatus (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:
-  __tdestroy (data.known_objects, free);
-  free (data.dirstreams);
-
-  return result;
-}
-
-
-
-/* Entry points.  */
-
-int
-FTW_NAME (const char *path, FTW_FUNC_T func, int descriptors)
-{
-  return ftw_startup (path, false, (func_callback_t) { .ftw_func = func },
-		      descriptors, 0);
-}
-
-#ifndef NFTW_OLD_NAME
-int
-NFTW_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags)
-{
-  return ftw_startup (path, true, (func_callback_t) { .nftw_func = 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, true, (func_callback_t) { .nftw_func = 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, true, (func_callback_t) { .nftw_func = func },
-		      descriptors, flags);
-}
-
-compat_symbol (libc, NFTW_OLD_NAME, NFTW_NAME, GLIBC_2_1);
-# endif
-#endif /* NFTW_OLD_NAME  */
diff --git a/io/ftw64-time64.c b/io/ftw64-time64.c
index 2df871f802..88f58fd85c 100644
--- a/io/ftw64-time64.c
+++ b/io/ftw64-time64.c
@@ -29,5 +29,5 @@
 # define FTW_FUNC_T     __ftw64_time64_func_t
 # define NFTW_FUNC_T    __nftw64_time64_func_t
 
-# include "ftw.c"
+# include "ftw-common.c"
 #endif
diff --git a/io/ftw64.c b/io/ftw64.c
index 0d7cb30091..d3cd14c21a 100644
--- a/io/ftw64.c
+++ b/io/ftw64.c
@@ -16,8 +16,8 @@
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
-#define FTW_NAME ftw64
-#define NFTW_NAME nftw64
+#define FTW_NAME __ftw64
+#define NFTW_NAME __nftw64
 #define NFTW_OLD_NAME __old_nftw64
 #define NFTW_NEW_NAME __new_nftw64
 #define INO_T ino64_t
@@ -28,4 +28,26 @@
 #define FTW_FUNC_T __ftw64_func_t
 #define NFTW_FUNC_T __nftw64_func_t
 
-#include "ftw.c"
+#define ftw __rename_ftw
+#define nftw __rename_nftw
+
+#include <shlib-compat.h>
+#include "ftw-common.c"
+
+#undef ftw
+#undef nftw
+
+weak_alias (__ftw64, ftw64)
+versioned_symbol (libc, __new_nftw64, nftw64, GLIBC_2_3_3);
+
+#if SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_3_3)
+compat_symbol (libc, __old_nftw64, nftw64, GLIBC_2_1);
+#endif
+
+#ifdef __OFF_T_MATCHES_OFF64_T
+weak_alias (__ftw64, ftw)
+versioned_symbol (libc, __new_nftw64, nftw, GLIBC_2_3_3);
+# if SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_3_3)
+compat_symbol (libc, __old_nftw64, nftw, GLIBC_2_1);
+# endif
+#endif
diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw.c b/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw.c
deleted file mode 100644
index 46389568b2..0000000000
--- a/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw.c
+++ /dev/null
@@ -1 +0,0 @@
-#include <io/ftw.c>
diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw64.c b/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw64.c
deleted file mode 100644
index cb02172b3e..0000000000
--- a/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw64.c
+++ /dev/null
@@ -1 +0,0 @@
-#include <io/ftw64.c>
diff --git a/sysdeps/unix/sysv/linux/x86_64/x32/ftw.c b/sysdeps/unix/sysv/linux/x86_64/x32/ftw.c
deleted file mode 100644
index a21dfe5690..0000000000
--- a/sysdeps/unix/sysv/linux/x86_64/x32/ftw.c
+++ /dev/null
@@ -1 +0,0 @@
-#include <sysdeps/wordsize-64/ftw.c>
diff --git a/sysdeps/unix/sysv/linux/x86_64/x32/ftw64.c b/sysdeps/unix/sysv/linux/x86_64/x32/ftw64.c
deleted file mode 100644
index 3c025b738a..0000000000
--- a/sysdeps/unix/sysv/linux/x86_64/x32/ftw64.c
+++ /dev/null
@@ -1 +0,0 @@
-#include <sysdeps/wordsize-64/ftw64.c>
diff --git a/sysdeps/wordsize-64/ftw.c b/sysdeps/wordsize-64/ftw.c
deleted file mode 100644
index ca19903799..0000000000
--- a/sysdeps/wordsize-64/ftw.c
+++ /dev/null
@@ -1,16 +0,0 @@
-#define ftw64 __rename_ftw64
-#define nftw64 __rename_nftw64
-
-#include "../../io/ftw.c"
-
-#undef ftw64
-#undef nftw64
-
-weak_alias (ftw, ftw64)
-strong_alias (__new_nftw, __new_nftw64)
-versioned_symbol (libc, __new_nftw64, nftw64, GLIBC_2_3_3);
-
-#if SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_3_3)
-strong_alias (__old_nftw, __old_nftw64)
-compat_symbol (libc, __old_nftw64, nftw64, GLIBC_2_1);
-#endif
diff --git a/sysdeps/wordsize-64/ftw64.c b/sysdeps/wordsize-64/ftw64.c
deleted file mode 100644
index 1cfcaadfd1..0000000000
--- a/sysdeps/wordsize-64/ftw64.c
+++ /dev/null
@@ -1 +0,0 @@
-/* Defined in ftw.c.  */
-- 
2.43.0



More information about the Libc-alpha mailing list