[{"id":3678845,"web_url":"http://patchwork.ozlabs.org/comment/3678845/","msgid":"<3aa40b5a-25c2-4640-aef2-17722fe59154@linaro.org>","list_archive_url":null,"date":"2026-04-17T17:31:16","subject":"Re: [PATCH 2/3] io: Consolidate ftw implementation","submitter":{"id":66065,"url":"http://patchwork.ozlabs.org/api/people/66065/","name":"Adhemerval Zanella Netto","email":"adhemerval.zanella@linaro.org"},"content":"This is a mechanical patch and I install if no one opposes it.\n\nOn 17/04/26 10:24, Adhemerval Zanella wrote:\n> Remove wordsize-64 and arch-specific implementations, for ABIs when\n> off_t is the same as off64_t (__OFF_T_MATCHES_OFF64_T) the ftw64.c\n> will create the requires aliases.\n> \n> The ftw.c implementation is moved to ftw-common.c to simplify\n> the __OFF_T_MATCHES_OFF64_T usage.\n> ---\n>  io/ftw-common.c                               | 969 ++++++++++++++++++\n>  io/ftw.c                                      | 958 +----------------\n>  io/ftw64-time64.c                             |   2 +-\n>  io/ftw64.c                                    |  28 +-\n>  sysdeps/unix/sysv/linux/mips/mips64/n64/ftw.c |   1 -\n>  .../unix/sysv/linux/mips/mips64/n64/ftw64.c   |   1 -\n>  sysdeps/unix/sysv/linux/x86_64/x32/ftw.c      |   1 -\n>  sysdeps/unix/sysv/linux/x86_64/x32/ftw64.c    |   1 -\n>  sysdeps/wordsize-64/ftw.c                     |  16 -\n>  sysdeps/wordsize-64/ftw64.c                   |   1 -\n>  10 files changed, 1003 insertions(+), 975 deletions(-)\n>  create mode 100644 io/ftw-common.c\n>  delete mode 100644 sysdeps/unix/sysv/linux/mips/mips64/n64/ftw.c\n>  delete mode 100644 sysdeps/unix/sysv/linux/mips/mips64/n64/ftw64.c\n>  delete mode 100644 sysdeps/unix/sysv/linux/x86_64/x32/ftw.c\n>  delete mode 100644 sysdeps/unix/sysv/linux/x86_64/x32/ftw64.c\n>  delete mode 100644 sysdeps/wordsize-64/ftw.c\n>  delete mode 100644 sysdeps/wordsize-64/ftw64.c\n> \n> diff --git a/io/ftw-common.c b/io/ftw-common.c\n> new file mode 100644\n> index 0000000000..07df0ab25a\n> --- /dev/null\n> +++ b/io/ftw-common.c\n> @@ -0,0 +1,969 @@\n> +/* File tree walker functions.\n> +   Copyright (C) 1996-2026 Free Software Foundation, Inc.\n> +   This file is part of the GNU C Library.\n> +\n> +   The GNU C Library is free software; you can redistribute it and/or\n> +   modify it under the terms of the GNU Lesser General Public\n> +   License as published by the Free Software Foundation; either\n> +   version 2.1 of the License, or (at your option) any later version.\n> +\n> +   The GNU C Library is distributed in the hope that it will be useful,\n> +   but WITHOUT ANY WARRANTY; without even the implied warranty of\n> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n> +   Lesser General Public License for more details.\n> +\n> +   You should have received a copy of the GNU Lesser General Public\n> +   License along with the GNU C Library; if not, see\n> +   <https://www.gnu.org/licenses/>.  */\n> +\n> +\n> +#include <assert.h>\n> +#include <dirent.h>\n> +#include <fcntl.h>\n> +#include <ftw.h>\n> +#include <not-cancel.h>\n> +#include <search.h>\n> +#include <unistd.h>\n> +#include <sys/param.h>\n> +\n> +#define NAMLEN(dirent) _D_EXACT_NAMLEN (dirent)\n> +\n> +/* Support for the LFS API version.  */\n> +#ifndef FTW_NAME\n> +# define FTW_NAME ftw\n> +# define NFTW_NAME nftw\n> +# define NFTW_OLD_NAME __old_nftw\n> +# define NFTW_NEW_NAME __new_nftw\n> +# define INO_T ino_t\n> +# define STRUCT_STAT stat\n> +# define LSTAT __lstat\n> +# define STAT __stat\n> +# define FSTATAT __fstatat\n> +# define FTW_FUNC_T __ftw_func_t\n> +# define NFTW_FUNC_T __nftw_func_t\n> +#endif\n> +\n> +/* We define PATH_MAX if the system does not provide a definition.\n> +   This does not artificially limit any operation.  PATH_MAX is simply\n> +   used as a guesstimate for the expected maximal path length.\n> +   Buffers will be enlarged if necessary.  */\n> +#ifndef PATH_MAX\n> +# define PATH_MAX 1024\n> +#endif\n> +\n> +struct dir_data\n> +{\n> +  DIR *stream;\n> +  int streamfd;\n> +  char *content;\n> +};\n> +\n> +struct known_object\n> +{\n> +  dev_t dev;\n> +  INO_T ino;\n> +};\n> +\n> +/* Represents the execution state of a directory processing frame within the\n> +   iterative file tree walk loop.\n> +\n> +   Because the tree traversal is implemented iteratively using a custom stack\n> +   rather than standard recursion, this state machine tracks the progress\n> +   of each directory currently being visited.  */\n> +enum ftw_frame_state\n> +{\n> +  /* The initial state of a newly pushed directory frame.  Attempts to open\n> +     the directory stream.  If successful, transitions to\n> +     FTW_STATE_STREAM_LOOP.  */\n> +  FTW_STATE_INIT = 0,\n> +\n> +  /* Iterating over the directory entries directly from the open DIR stream\n> +     (using readdir).  If a subdirectory is encountered and needs to be\n> +     descended into, a new frame is added to the stack and execution pauses\n> +     here.  Transitions to FTW_STATE_CONTENT_LOOP if the stream was closed\n> +     and cached to free up file descriptors, or FTW_STATE_CLEANUP when\n> +     done.  */\n> +  FTW_STATE_STREAM_LOOP,\n> +\n> +  /* Iterating over directory entries from a cached memory buffer.  This state\n> +     is used as a fallback when the original DIR stream had to be closed\n> +     prematurely to prevent file descriptor exhaustion while descending into\n> +     deeply nested child directories.  Transitions to FTW_STATE_CLEANUP when\n> +     all cached entries are processed.  */\n> +  FTW_STATE_CONTENT_LOOP,\n> +\n> +  /* The final state, handles resource deallocation (closing remaining\n> +     streams, freeing cached content buffers), triggering post-traversal\n> +     callbacks (like FTW_DP for FTW_DEPTH walks), and restoring the\n> +     previous working directory if FTW_CHDIR was used.  */\n> +  FTW_STATE_CLEANUP\n> +};\n> +\n> +/* Keep track of visited directories.  */\n> +struct ftw_frame\n> +{\n> +  struct dir_data dir;\n> +  struct STRUCT_STAT st;\n> +  int previous_base;\n> +  char *runp;\n> +  enum ftw_frame_state state;\n> +};\n> +\n> +struct ftw_stack\n> +{\n> +  struct ftw_frame **stack;\n> +  size_t num_blocks;\n> +  ssize_t top;\n> +};\n> +\n> +typedef union\n> +{\n> +  NFTW_FUNC_T nftw_func;\n> +  FTW_FUNC_T ftw_func;\n> +} func_callback_t;\n> +\n> +struct ftw_data\n> +{\n> +  /* Array with pointers to open directory streams.  */\n> +  struct dir_data **dirstreams;\n> +  size_t actdir;\n> +  size_t maxdir;\n> +\n> +  /* Buffer containing name of currently processed object.  */\n> +  char *dirbuf;\n> +  size_t dirbufsize;\n> +\n> +  /* Passed as fourth argument to `nftw' callback.  The `base' member\n> +     tracks the content of the `dirbuf'.  */\n> +  struct FTW ftw;\n> +\n> +  /* Flags passed to `nftw' function.  0 for `ftw'.  */\n> +  int flags;\n> +\n> +  /* Conversion array for flag values.  It is the identity mapping for\n> +     `nftw' calls, otherwise it maps the values to those known by\n> +     `ftw'.  */\n> +  const int *cvt_arr;\n> +\n> +  /* Callback function.  We always use the `nftw' form.  */\n> +  bool is_nftw;\n> +  func_callback_t func;\n> +\n> +  /* Device of starting point.  Needed for FTW_MOUNT.  */\n> +  dev_t dev;\n> +\n> +  /* Data structure for keeping fingerprints of already processed\n> +     object.  This is needed when not using FTW_PHYS.  */\n> +  void *known_objects;\n> +};\n> +#define CALL_FUNC(__ftw_data, __fp, __sb, __f, __ftw)                            \\\n> +  ((__ftw_data)->is_nftw ? (__ftw_data)->func.nftw_func (__fp, __sb, __f, __ftw) \\\n> +                         : (__ftw_data)->func.ftw_func (__fp, __sb, __f))\n> +\n> +static bool\n> +ftw_allocate (struct ftw_data *data, size_t newsize)\n> +{\n> +  void *newp = realloc (data->dirstreams, data->maxdir\n> +\t\t\t\t\t  * sizeof (struct dir_data *)\n> +\t\t\t\t\t  + newsize);\n> +  if (newp == NULL)\n> +    return false;\n> +  data->dirstreams = newp;\n> +  data->dirbufsize = newsize;\n> +  data->dirbuf = (char *) data->dirstreams\n> +\t\t + data->maxdir * sizeof (struct dir_data *);\n> +  return true;\n> +}\n> +\n> +/* Internally we use the FTW_* constants used for `nftw'.  When invoked\n> +   as `ftw', map each flag to the subset of values used by `ftw'.  */\n> +static const int nftw_arr[] =\n> +{\n> +  FTW_F, FTW_D, FTW_DNR, FTW_NS, FTW_SL, FTW_DP, FTW_SLN\n> +};\n> +\n> +static const int ftw_arr[] =\n> +{\n> +  FTW_F, FTW_D, FTW_DNR, FTW_NS, FTW_F, FTW_D, FTW_NS\n> +};\n> +\n> +\n> +static int\n> +object_compare (const void *p1, const void *p2)\n> +{\n> +  /* We don't need a sophisticated and useful comparison.  We are only\n> +     interested in equality.  However, we must be careful not to\n> +     accidentally compare `holes' in the structure.  */\n> +  const struct known_object *kp1 = p1, *kp2 = p2;\n> +  int cmp1;\n> +  cmp1 = (kp1->ino > kp2->ino) - (kp1->ino < kp2->ino);\n> +  if (cmp1 != 0)\n> +    return cmp1;\n> +  return (kp1->dev > kp2->dev) - (kp1->dev < kp2->dev);\n> +}\n> +\n> +\n> +static int\n> +add_object (struct ftw_data *data, struct STRUCT_STAT *st)\n> +{\n> +  struct known_object *newp = malloc (sizeof (struct known_object));\n> +  if (newp == NULL)\n> +    return -1;\n> +  newp->dev = st->st_dev;\n> +  newp->ino = st->st_ino;\n> +  return __tsearch (newp, &data->known_objects, object_compare) ? 0 : -1;\n> +}\n> +\n> +\n> +static inline int\n> +find_object (struct ftw_data *data, struct STRUCT_STAT *st)\n> +{\n> +  struct known_object obj;\n> +  obj.dev = st->st_dev;\n> +  obj.ino = st->st_ino;\n> +  return __tfind (&obj, &data->known_objects, object_compare) != NULL;\n> +}\n> +\n> +\n> +static inline int\n> +open_dir_stream (int *dfdp, struct ftw_data *data, struct dir_data *dirp)\n> +{\n> +  int result = 0;\n> +\n> +  if (data->dirstreams[data->actdir] != NULL)\n> +    {\n> +      /* Oh, oh.  We must close this stream.  Get all remaining\n> +\t entries and store them as a list in the `content' member of\n> +\t the `struct dir_data' variable.  */\n> +      size_t bufsize = 1024;\n> +      char *buf = malloc (bufsize);\n> +\n> +      if (buf == NULL)\n> +\tresult = -1;\n> +      else\n> +\t{\n> +\t  DIR *st = data->dirstreams[data->actdir]->stream;\n> +\t  struct dirent64 *d;\n> +\t  size_t actsize = 0;\n> +\n> +\t  while ((d = __readdir64 (st)) != NULL)\n> +\t    {\n> +\t      size_t this_len = NAMLEN (d);\n> +\t      if (actsize + this_len + 2 >= bufsize)\n> +\t\t{\n> +\t\t  char *newp;\n> +\t\t  bufsize += MAX (1024, 2 * this_len);\n> +\t\t  newp = (char *) realloc (buf, bufsize);\n> +\t\t  if (newp == NULL)\n> +\t\t    {\n> +\t\t      /* No more memory.  */\n> +\t\t      free (buf);\n> +\t\t      return -1;\n> +\t\t    }\n> +\t\t  buf = newp;\n> +\t\t}\n> +\n> +\t      *((char *) __mempcpy (buf + actsize, d->d_name, this_len))\n> +\t\t= '\\0';\n> +\t      actsize += this_len + 1;\n> +\t    }\n> +\n> +\t  /* Terminate the list with an additional NUL byte.  */\n> +\t  buf[actsize++] = '\\0';\n> +\n> +\t  /* Shrink the buffer to what we actually need.  */\n> +\t  void *content = realloc (buf, actsize);\n> +\t  data->dirstreams[data->actdir]->content = content;\n> +\t  if (content == NULL)\n> +\t    {\n> +\t      free (buf);\n> +\t      result = -1;\n> +\t    }\n> +\t  else\n> +\t    {\n> +\t      __closedir (st);\n> +\t      data->dirstreams[data->actdir]->stream = NULL;\n> +\t      data->dirstreams[data->actdir]->streamfd = -1;\n> +\t      data->dirstreams[data->actdir] = NULL;\n> +\t    }\n> +\t}\n> +    }\n> +\n> +  /* Open the new stream.  */\n> +  if (result == 0)\n> +    {\n> +      assert (data->dirstreams[data->actdir] == NULL);\n> +\n> +      if (dfdp != NULL && *dfdp != -1)\n> +\t{\n> +\t  int fd = __openat64_nocancel (*dfdp, data->dirbuf + data->ftw.base,\n> +\t\t\t\t\tO_RDONLY | O_DIRECTORY | O_NDELAY);\n> +\t  dirp->stream = NULL;\n> +\t  if (fd != -1 && (dirp->stream = __fdopendir (fd)) == NULL)\n> +\t    __close_nocancel_nostatus (fd);\n> +\t}\n> +      else\n> +\t{\n> +\t  const char *name;\n> +\n> +\t  if (data->flags & FTW_CHDIR)\n> +\t    {\n> +\t      name = data->dirbuf + data->ftw.base;\n> +\t      if (name[0] == '\\0')\n> +\t\tname = \".\";\n> +\t    }\n> +\t  else\n> +\t    name = data->dirbuf;\n> +\n> +\t  dirp->stream = __opendir (name);\n> +\t}\n> +\n> +      if (dirp->stream == NULL)\n> +\tresult = -1;\n> +      else\n> +\t{\n> +\t  dirp->streamfd = __dirfd (dirp->stream);\n> +\t  dirp->content = NULL;\n> +\t  data->dirstreams[data->actdir] = dirp;\n> +\n> +\t  if (++data->actdir == data->maxdir)\n> +\t    data->actdir = 0;\n> +\t}\n> +    }\n> +\n> +  return result;\n> +}\n> +\n> +\n> +static int\n> +process_entry (struct ftw_data *data, struct dir_data *dir, const char *name,\n> +\t       size_t namlen, struct STRUCT_STAT *out_st, bool *descend)\n> +{\n> +  struct STRUCT_STAT st;\n> +  int result = 0;\n> +  int flag = 0;\n> +  size_t new_buflen;\n> +\n> +  *descend = false;\n> +\n> +  if (name[0] == '.' && (name[1] == '\\0'\n> +\t\t\t || (name[1] == '.' && name[2] == '\\0')))\n> +    /* Don't process the \".\" and \"..\" entries.  */\n> +    return 0;\n> +\n> +  new_buflen = data->ftw.base + namlen + 2;\n> +  if (data->dirbufsize < new_buflen\n> +      && !ftw_allocate (data, 2 * new_buflen))\n> +    return -1;\n> +\n> +  *((char *) __mempcpy (data->dirbuf + data->ftw.base, name, namlen)) = '\\0';\n> +\n> +  int statres;\n> +  if (dir->streamfd != -1)\n> +    statres = FSTATAT (dir->streamfd, name, &st,\n> +\t\t       (data->flags & FTW_PHYS) ? AT_SYMLINK_NOFOLLOW : 0);\n> +  else\n> +    {\n> +      if ((data->flags & FTW_CHDIR) == 0)\n> +\tname = data->dirbuf;\n> +\n> +      statres = ((data->flags & FTW_PHYS)\n> +\t\t ? LSTAT (name, &st)\n> +\t\t : STAT (name, &st));\n> +    }\n> +\n> +  if (statres < 0)\n> +    {\n> +      if (errno != EACCES && errno != ENOENT)\n> +\tresult = -1;\n> +      else if (data->flags & FTW_PHYS)\n> +\tflag = FTW_NS;\n> +      else\n> +\t{\n> +\t  /* Old code left ST undefined for dangling DT_LNK without\n> +\t     FTW_PHYS set; a clarification at the POSIX level suggests\n> +\t     it should contain information about the link (ala lstat).\n> +\t     We do our best to fill in what data we can.  */\n> +\t  if (dir->streamfd != -1)\n> +\t    statres = FSTATAT (dir->streamfd, name, &st,\n> +\t\t\t       AT_SYMLINK_NOFOLLOW);\n> +\t  else\n> +\t    statres = LSTAT (name, &st);\n> +\t  if (statres == 0 && S_ISLNK (st.st_mode))\n> +\t    flag = FTW_SLN;\n> +\t  else\n> +\t    flag = FTW_NS;\n> +\t}\n> +    }\n> +  else\n> +    {\n> +      if (S_ISDIR (st.st_mode))\n> +\tflag = FTW_D;\n> +      else if (S_ISLNK (st.st_mode))\n> +\tflag = FTW_SL;\n> +      else\n> +\tflag = FTW_F;\n> +    }\n> +\n> +  if (result == 0\n> +      && (flag == FTW_NS\n> +\t  || !(data->flags & FTW_MOUNT) || st.st_dev == data->dev))\n> +    {\n> +      if (flag == FTW_D)\n> +\t{\n> +\t  if ((data->flags & FTW_PHYS)\n> +\t      || (!find_object (data, &st)\n> +\t\t  /* Remember the object.  */\n> +\t\t  && (result = add_object (data, &st)) == 0))\n> +\t    {\n> +               *out_st = st;\n> +               *descend = true;\n> +\t    }\n> +\t}\n> +      else\n> +\tresult = CALL_FUNC (data, data->dirbuf, &st, data->cvt_arr[flag],\n> +\t\t\t    &data->ftw);\n> +    }\n> +\n> +  if ((data->flags & FTW_ACTIONRETVAL) && result == FTW_SKIP_SUBTREE)\n> +    result = 0;\n> +\n> +  return result;\n> +}\n> +\n> +\n> +/* The ftw_frame are kept as chunked array to minimize the reallocation cost\n> +   when the stack grows (since it contains STRUCT_STAT and extra metadata).\n> +   New chunks of ftw_framw are allocated and only freed when ftw returns.  */\n> +enum\n> +{\n> +  FTW_STACK_CHUNK_BLOCKS  = 1,  /* Number of initial allocated chunks.  */\n> +  FTW_STACK_CHUNK_SIZE    = 32  /* Number of stack frames allocated per\n> +\t\t\t\t   chunk.  */\n> +};\n> +\n> +static inline struct ftw_frame *\n> +frame_stack_get (struct ftw_stack *ftwst, int adj)\n> +{\n> +  return &ftwst->stack[(ftwst->top + adj) / FTW_STACK_CHUNK_SIZE]\n> +    [(ftwst->top + adj) % FTW_STACK_CHUNK_SIZE];\n> +}\n> +\n> +static inline void\n> +frame_stack_reset_top (struct ftw_stack *fwtst, const struct STRUCT_STAT *st)\n> +{\n> +  struct ftw_frame *frame = frame_stack_get (fwtst, 0);\n> +  frame->st = *st;\n> +  frame->state = FTW_STATE_INIT;\n> +  frame->dir.stream = NULL;\n> +  frame->dir.content = NULL;\n> +  frame->dir.streamfd = -1;\n> +}\n> +\n> +static bool\n> +frame_stack_init (struct ftw_stack *ftwst, const struct STRUCT_STAT *st)\n> +{\n> +  ftwst->num_blocks = FTW_STACK_CHUNK_BLOCKS;\n> +  ftwst->stack = malloc (FTW_STACK_CHUNK_BLOCKS * sizeof (*ftwst->stack));\n> +  if (ftwst->stack == NULL)\n> +    return false;\n> +\n> +  ftwst->stack[0] = malloc (FTW_STACK_CHUNK_SIZE * sizeof (struct ftw_frame));\n> +  if (ftwst->stack[0] == NULL)\n> +    {\n> +      free (ftwst->stack);\n> +      return false;\n> +    }\n> +\n> +  ftwst->top = 0;\n> +  frame_stack_reset_top (ftwst, st);\n> +  return true;\n> +}\n> +\n> +static void\n> +frame_stack_free (struct ftw_stack *ftwst)\n> +{\n> +  for (size_t i = 0; i < ftwst->num_blocks; i++)\n> +    free (ftwst->stack[i]);\n> +  free (ftwst->stack);\n> +}\n> +\n> +static bool\n> +frame_stack_add (struct ftw_stack *ftwst, const struct STRUCT_STAT *st)\n> +{\n> +  if (ftwst->top + 1 >= ftwst->num_blocks * FTW_STACK_CHUNK_SIZE)\n> +    {\n> +      size_t new_blocks = ftwst->num_blocks + 1;\n> +      struct ftw_frame **new_stack = realloc (\n> +\t  ftwst->stack, new_blocks * sizeof (*ftwst->stack));\n> +\n> +      if (new_stack == NULL)\n> +\treturn false;\n> +      ftwst->stack = new_stack;\n> +      ftwst->stack[ftwst->num_blocks] = malloc (\n> +\t  FTW_STACK_CHUNK_SIZE * sizeof (struct ftw_frame));\n> +      if (ftwst->stack[ftwst->num_blocks] == NULL)\n> +\treturn false;\n> +      ftwst->num_blocks = new_blocks;\n> +    }\n> +  ftwst->top++;\n> +  frame_stack_reset_top (ftwst, st);\n> +  return true;\n> +}\n> +\n> +static void\n> +frame_closedir (struct ftw_data *data, struct ftw_frame *frame)\n> +{\n> +  int save_err = errno;\n> +  assert (frame->dir.content == NULL);\n> +  __closedir (frame->dir.stream);\n> +  frame->dir.streamfd = -1;\n> +  __set_errno (save_err);\n> +  if (data->actdir-- == 0)\n> +    data->actdir = data->maxdir - 1;\n> +  data->dirstreams[data->actdir] = NULL;\n> +  frame->dir.stream = NULL;\n> +}\n> +\n> +static int\n> +ftw_dir (struct ftw_data *data, const struct STRUCT_STAT *st)\n> +{\n> +  struct ftw_stack ftwst;\n> +  if (!frame_stack_init (&ftwst, st))\n> +    return -1;\n> +\n> +  int result = 0;\n> +\n> +  while (ftwst.top >= 0)\n> +    {\n> +      struct ftw_frame *frame = frame_stack_get (&ftwst, 0);\n> +      struct dir_data *old_dir = (ftwst.top > 0)\n> +\t? &frame_stack_get (&ftwst, -1)->dir : NULL;\n> +\n> +      if (frame->state == FTW_STATE_INIT)\n> +\t{\n> +\t  frame->previous_base = data->ftw.base;\n> +\t  result = open_dir_stream (\n> +\t      old_dir == NULL ? NULL : &old_dir->streamfd, data, &frame->dir);\n> +\t  if (result != 0)\n> +\t    {\n> +\t      if (errno == EACCES)\n> +\t\tresult = CALL_FUNC (data, data->dirbuf, &frame->st, FTW_DNR,\n> +\t\t\t\t    &data->ftw);\n> +\t      ftwst.top--;\n> +\t      /* Intercept FTW_SKIP_SUBTREE when popping frame */\n> +\t      if (ftwst.top >= 0 && (data->flags & FTW_ACTIONRETVAL)\n> +\t\t  && result == FTW_SKIP_SUBTREE)\n> +\t\tresult = 0;\n> +\t      continue;\n> +\t    }\n> +\n> +\t  if (!(data->flags & FTW_DEPTH))\n> +\t    {\n> +\t      result = CALL_FUNC (data, data->dirbuf, &frame->st, FTW_D,\n> +\t\t\t\t  &data->ftw);\n> +\t      if (result != 0)\n> +\t\tgoto state0_fail;\n> +\t    }\n> +\n> +\t  if (data->flags & FTW_CHDIR)\n> +\t    {\n> +\t      if (__fchdir (__dirfd (frame->dir.stream)) < 0)\n> +\t\t{\n> +\t\t  result = -1;\n> +\t\tstate0_fail:\n> +\t\t  frame_closedir (data, frame);\n> +\t\t  ftwst.top--;\n> +\t\t  /* Intercept FTW_SKIP_SUBTREE when popping frame.  */\n> +\t\t  if (ftwst.top >= 0 && (data->flags & FTW_ACTIONRETVAL)\n> +\t\t      && result == FTW_SKIP_SUBTREE)\n> +\t\t    result = 0;\n> +\t\t  continue;\n> +\t\t}\n> +\t    }\n> +\n> +\t  ++data->ftw.level;\n> +\t  char *startp = strchr (data->dirbuf, '\\0');\n> +\t  assert (startp != data->dirbuf);\n> +\t  if (startp[-1] != '/')\n> +\t    *startp++ = '/';\n> +\t  data->ftw.base = startp - data->dirbuf;\n> +\n> +\t  frame->state = FTW_STATE_STREAM_LOOP;\n> +\t  frame->runp = frame->dir.content;\n> +\t}\n> +      else if (frame->state == FTW_STATE_STREAM_LOOP)\n> +\t{\n> +\t  if (result != 0)\n> +\t    {\n> +\t      frame->state = FTW_STATE_CLEANUP;\n> +\t      continue;\n> +\t    }\n> +\n> +\t  if (frame->dir.stream == NULL)\n> +\t    {\n> +\t      frame->state = FTW_STATE_CONTENT_LOOP;\n> +\t      frame->runp = frame->dir.content;\n> +\t      continue;\n> +\t    }\n> +\n> +\t  struct dirent64 *d = __readdir64 (frame->dir.stream);\n> +\t  if (d != NULL)\n> +\t    {\n> +\t      struct STRUCT_STAT child_st;\n> +\t      bool descend = false;\n> +\t      result = process_entry (data, &frame->dir, d->d_name, NAMLEN (d),\n> +\t\t\t\t      &child_st, &descend);\n> +\n> +\t      if (result == 0 && descend)\n> +\t\t{\n> +\t\t  if (!frame_stack_add (&ftwst, &child_st))\n> +\t\t    {\n> +\t\t      result = -1;\n> +\t\t      frame->state = FTW_STATE_CLEANUP;\n> +\t\t    }\n> +\t\t  continue;\n> +\t\t}\n> +\t      else if (result != 0)\n> +\t\t{\n> +\t\t  frame->state = FTW_STATE_CLEANUP;\n> +\t\t  continue;\n> +\t\t}\n> +\t    }\n> +\t  else\n> +\t    frame->state = FTW_STATE_CLEANUP;\n> +\t}\n> +      else if (frame->state == FTW_STATE_CONTENT_LOOP)\n> +\t{\n> +\t  /* Check if we are safely positioned to process the starting path.\n> +\t     The 'result' variable here comes from one of two places:\n> +\n> +\t     1. Initialization: defaults to 0 at the top of ftw_startup.  If\n> +\t        the FTW_CHDIR flag was NOT passed, it remains 0, meaning we\n> +\t\tare good to go.\n> +\n> +\t     2. Directory Change: If FTW_CHDIR WAS passed, 'result' holds the\n> +\t        return value of the preceding __chdir() call (either moving\n> +\t\tto \"/\" or the parsed base directory).\n> +\n> +\t     If 'result' is 0, the setup succeeded (or wasn't needed) and we\n> +\t     can safely stat the initial object.  Othewise, the chdir failed,\n> +\t     so we skip processing and fall through to the cleanup phase.  */\n> +\t  if (result != 0)\n> +\t    {\n> +\t      frame->state = FTW_STATE_CLEANUP;\n> +\t      continue;\n> +\t    }\n> +\n> +\t  if (frame->runp != NULL && *frame->runp != '\\0')\n> +\t    {\n> +\t      char *endp = strchr (frame->runp, '\\0');\n> +\t      struct STRUCT_STAT child_st;\n> +\t      bool descend = false;\n> +\n> +\t      result = process_entry (data, &frame->dir, frame->runp,\n> +\t\t\t\t      endp - frame->runp, &child_st,\n> +\t\t\t\t      &descend);\n> +\t      frame->runp = endp + 1;\n> +\n> +\t      if (result == 0 && descend)\n> +\t\t{\n> +\t\t  if (!frame_stack_add (&ftwst, &child_st))\n> +\t\t    {\n> +\t\t      result = -1;\n> +\t\t      frame->state = FTW_STATE_CLEANUP;\n> +\t\t    }\n> +\t\t  continue;\n> +\t\t}\n> +\t      else if (result != 0)\n> +\t\t{\n> +\t\t  frame->state = FTW_STATE_CLEANUP;\n> +\t\t  continue;\n> +\t\t}\n> +\t    }\n> +\t  else\n> +\t    frame->state = FTW_STATE_CLEANUP;\n> +\t}\n> +      else if (frame->state == FTW_STATE_CLEANUP)\n> +\t{\n> +\t  if (frame->dir.stream != NULL)\n> +\t    frame_closedir (data, frame);\n> +\t  else if (frame->dir.content != NULL)\n> +\t    {\n> +\t      free (frame->dir.content);\n> +\t      frame->dir.content = NULL;\n> +\t    }\n> +\n> +\t  if ((data->flags & FTW_ACTIONRETVAL) && result == FTW_SKIP_SIBLINGS)\n> +\t    result = 0;\n> +\n> +\t  data->dirbuf[data->ftw.base - 1] = '\\0';\n> +\t  --data->ftw.level;\n> +\t  data->ftw.base = frame->previous_base;\n> +\n> +\t  if (result == 0 && (data->flags & FTW_DEPTH))\n> +\t    result\n> +\t\t= CALL_FUNC (data, data->dirbuf, &frame->st, FTW_DP,\n> +\t\t\t     &data->ftw);\n> +\n> +\t  if (old_dir != NULL && (data->flags & FTW_CHDIR)\n> +\t      && (result == 0\n> +\t\t  || ((data->flags & FTW_ACTIONRETVAL)\n> +\t\t      && (result != -1 && result != FTW_STOP))))\n> +\t    {\n> +\t      int done = 0;\n> +\t      if (old_dir->stream != NULL)\n> +\t\tif (__fchdir (__dirfd (old_dir->stream)) == 0)\n> +\t\t  done = 1;\n> +\n> +\t      if (!done)\n> +\t\t{\n> +\t\t  if (data->ftw.base == 1)\n> +\t\t    {\n> +\t\t      if (__chdir (\"/\") < 0)\n> +\t\t\tresult = -1;\n> +\t\t    }\n> +\t\t  else if (__chdir (\"..\") < 0)\n> +\t\t    result = -1;\n> +\t\t}\n> +\t    }\n> +\n> +\t  ftwst.top--;\n> +\t  /* Intercept FTW_SKIP_SUBTREE when popping frame.  */\n> +\t  if (ftwst.top >= 0 && (data->flags & FTW_ACTIONRETVAL)\n> +\t      && result == FTW_SKIP_SUBTREE)\n> +\t    result = 0;\n> +\t}\n> +    }\n> +\n> +  frame_stack_free (&ftwst);\n> +\n> +  return result;\n> +}\n> +\n> +\n> +static int\n> +ftw_startup (const char *dir, bool is_nftw, func_callback_t func,\n> +\t     int descriptors, int flags)\n> +{\n> +  struct ftw_data data = { .dirstreams = NULL };\n> +  struct STRUCT_STAT st;\n> +  int result = 0;\n> +  int cwdfd = -1;\n> +  char *cwd = NULL;\n> +  char *cp;\n> +\n> +  /* First make sure the parameters are reasonable.  */\n> +  if (dir[0] == '\\0')\n> +    {\n> +      __set_errno (ENOENT);\n> +      return -1;\n> +    }\n> +\n> +  data.maxdir = descriptors < 1 ? 1 : descriptors;\n> +  data.actdir = 0;\n> +  /* PATH_MAX is always defined when we get here.  */\n> +  if (!ftw_allocate (&data, MAX (2 * strlen (dir), PATH_MAX)))\n> +    return -1;\n> +  memset (data.dirstreams, '\\0', data.maxdir * sizeof (struct dir_data *));\n> +  cp = __stpcpy (data.dirbuf, dir);\n> +  /* Strip trailing slashes.  */\n> +  while (cp > data.dirbuf + 1 && cp[-1] == '/')\n> +    --cp;\n> +  *cp = '\\0';\n> +\n> +  data.ftw.level = 0;\n> +\n> +  /* Find basename.  */\n> +  while (cp > data.dirbuf && cp[-1] != '/')\n> +    --cp;\n> +  data.ftw.base = cp - data.dirbuf;\n> +\n> +  data.flags = flags;\n> +\n> +  data.is_nftw = is_nftw;\n> +  data.func = func;\n> +\n> +  /* Since we internally use the complete set of FTW_* values we need\n> +     to reduce the value range before calling a `ftw' callback.  */\n> +  data.cvt_arr = is_nftw ? nftw_arr : ftw_arr;\n> +\n> +  /* No object known so far.  */\n> +  data.known_objects = NULL;\n> +\n> +  /* Now go to the directory containing the initial file/directory.  */\n> +  if (flags & FTW_CHDIR)\n> +    {\n> +      /* We have to be able to go back to the current working\n> +\t directory.  The best way to do this is to use a file\n> +\t descriptor.  */\n> +      cwdfd = __open (\".\", O_RDONLY | O_DIRECTORY);\n> +      if (cwdfd == -1)\n> +\t{\n> +\t  /* Try getting the directory name.  This can be needed if\n> +\t     the current directory is executable but not readable.  */\n> +\t  if (errno == EACCES)\n> +\t    /* GNU extension ahead.  */\n> +\t    cwd =  __getcwd (NULL, 0);\n> +\n> +\t  if (cwd == NULL)\n> +\t    goto out_fail;\n> +\t}\n> +      else if (data.maxdir > 1)\n> +\t/* Account for the file descriptor we use here.  */\n> +\t--data.maxdir;\n> +\n> +      if (data.ftw.base > 0)\n> +\t{\n> +\t  /* Change to the directory the file is in.  In data.dirbuf\n> +\t     we have a writable copy of the file name.  Just NUL\n> +\t     terminate it for now and change the directory.  */\n> +\t  if (data.ftw.base == 1)\n> +\t    /* I.e., the file is in the root directory.  */\n> +\t    result = __chdir (\"/\");\n> +\t  else\n> +\t    {\n> +\t      char ch = data.dirbuf[data.ftw.base - 1];\n> +\t      data.dirbuf[data.ftw.base - 1] = '\\0';\n> +\t      result = __chdir (data.dirbuf);\n> +\t      data.dirbuf[data.ftw.base - 1] = ch;\n> +\t    }\n> +\t}\n> +    }\n> +\n> +  /* Get stat info for start directory.  */\n> +  if (result == 0)\n> +    {\n> +      const char *name;\n> +\n> +      if (data.flags & FTW_CHDIR)\n> +\t{\n> +\t  name = data.dirbuf + data.ftw.base;\n> +\t  if (name[0] == '\\0')\n> +\t    name = \".\";\n> +\t}\n> +      else\n> +\tname = data.dirbuf;\n> +\n> +      if (((flags & FTW_PHYS)\n> +\t   ? LSTAT (name, &st)\n> +\t   : STAT (name, &st)) < 0)\n> +\t{\n> +\t  if (!(flags & FTW_PHYS)\n> +\t      && errno == ENOENT\n> +\t      && LSTAT (name, &st) == 0\n> +\t      && S_ISLNK (st.st_mode))\n> +\t    result = CALL_FUNC (&data, data.dirbuf, &st, data.cvt_arr[FTW_SLN],\n> +\t\t\t\t&data.ftw);\n> +\t  else\n> +\t    /* No need to call the callback since we cannot say anything\n> +\t       about the object.  */\n> +\t    result = -1;\n> +\t}\n> +      else\n> +\t{\n> +\t  if (S_ISDIR (st.st_mode))\n> +\t    {\n> +\t      /* Remember the device of the initial directory in case\n> +\t\t FTW_MOUNT is given.  */\n> +\t      data.dev = st.st_dev;\n> +\n> +\t      /* We know this directory now.  */\n> +\t      if (!(flags & FTW_PHYS))\n> +\t\tresult = add_object (&data, &st);\n> +\n> +\t      if (result == 0)\n> +\t\tresult = ftw_dir (&data, &st);\n> +\t    }\n> +\t  else\n> +\t    {\n> +\t      int flag = S_ISLNK (st.st_mode) ? FTW_SL : FTW_F;\n> +\n> +\t      result = CALL_FUNC (&data, data.dirbuf, &st, data.cvt_arr[flag],\n> +\t\t\t\t  &data.ftw);\n> +\t    }\n> +\t}\n> +\n> +      if ((flags & FTW_ACTIONRETVAL)\n> +\t  && (result == FTW_SKIP_SUBTREE || result == FTW_SKIP_SIBLINGS))\n> +\tresult = 0;\n> +    }\n> +\n> +  /* Return to the start directory (if necessary).  */\n> +  if (cwdfd != -1)\n> +    {\n> +      int save_err = errno;\n> +      __fchdir (cwdfd);\n> +      __close_nocancel_nostatus (cwdfd);\n> +      __set_errno (save_err);\n> +    }\n> +  else if (cwd != NULL)\n> +    {\n> +      int save_err = errno;\n> +      __chdir (cwd);\n> +      free (cwd);\n> +      __set_errno (save_err);\n> +    }\n> +\n> +  /* Free all memory.  */\n> + out_fail:\n> +  __tdestroy (data.known_objects, free);\n> +  free (data.dirstreams);\n> +\n> +  return result;\n> +}\n> +\n> +\n> +\n> +/* Entry points.  */\n> +\n> +int\n> +FTW_NAME (const char *path, FTW_FUNC_T func, int descriptors)\n> +{\n> +  return ftw_startup (path, false, (func_callback_t) { .ftw_func = func },\n> +\t\t      descriptors, 0);\n> +}\n> +\n> +#ifndef NFTW_OLD_NAME\n> +int\n> +NFTW_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags)\n> +{\n> +  return ftw_startup (path, true, (func_callback_t) { .nftw_func = func },\n> +\t\t      descriptors, flags);\n> +}\n> +#else\n> +\n> +# include <shlib-compat.h>\n> +\n> +int NFTW_NEW_NAME (const char *, NFTW_FUNC_T, int, int);\n> +\n> +int\n> +NFTW_NEW_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags)\n> +{\n> +  if (flags\n> +      & ~(FTW_PHYS | FTW_MOUNT | FTW_CHDIR | FTW_DEPTH | FTW_ACTIONRETVAL))\n> +    {\n> +      __set_errno (EINVAL);\n> +      return -1;\n> +    }\n> +  return ftw_startup (path, true, (func_callback_t) { .nftw_func = func },\n> +\t\t      descriptors, flags);\n> +}\n> +\n> +# if SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_3_3)\n> +\n> +/* Older nftw* version just ignored all unknown flags.  */\n> +\n> +int NFTW_OLD_NAME (const char *, NFTW_FUNC_T, int, int);\n> +\n> +int\n> +attribute_compat_text_section\n> +NFTW_OLD_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags)\n> +{\n> +  flags &= (FTW_PHYS | FTW_MOUNT | FTW_CHDIR | FTW_DEPTH);\n> +  return ftw_startup (path, true, (func_callback_t) { .nftw_func = func },\n> +\t\t      descriptors, flags);\n> +}\n> +\n> +# endif\n> +#endif /* NFTW_OLD_NAME  */\n> diff --git a/io/ftw.c b/io/ftw.c\n> index 726c430eaf..ed0eeb3904 100644\n> --- a/io/ftw.c\n> +++ b/io/ftw.c\n> @@ -1,5 +1,5 @@\n> -/* File tree walker functions.\n> -   Copyright (C) 1996-2026 Free Software Foundation, Inc.\n> +/* File tree traversal functions LFS version.\n> +   Copyright (C) 2026 Free Software Foundation, Inc.\n>     This file is part of the GNU C Library.\n>  \n>     The GNU C Library is free software; you can redistribute it and/or\n> @@ -16,956 +16,14 @@\n>     License along with the GNU C Library; if not, see\n>     <https://www.gnu.org/licenses/>.  */\n>  \n> +#include <sys/types.h>\n>  \n> -#include <assert.h>\n> -#include <dirent.h>\n> -#include <fcntl.h>\n> -#include <ftw.h>\n> -#include <not-cancel.h>\n> -#include <search.h>\n> -#include <unistd.h>\n> -#include <sys/param.h>\n> +#ifndef __OFF_T_MATCHES_OFF64_T\n> +# include \"ftw-common.c\"\n>  \n> -#define NAMLEN(dirent) _D_EXACT_NAMLEN (dirent)\n> -\n> -/* Support for the LFS API version.  */\n> -#ifndef FTW_NAME\n> -# define FTW_NAME ftw\n> -# define NFTW_NAME nftw\n> -# define NFTW_OLD_NAME __old_nftw\n> -# define NFTW_NEW_NAME __new_nftw\n> -# define INO_T ino_t\n> -# define STRUCT_STAT stat\n> -# define LSTAT __lstat\n> -# define STAT __stat\n> -# define FSTATAT __fstatat\n> -# define FTW_FUNC_T __ftw_func_t\n> -# define NFTW_FUNC_T __nftw_func_t\n> +versioned_symbol (libc, __new_nftw, nftw, GLIBC_2_3_3);\n> +#if SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_3_3)\n> +compat_symbol (libc, __old_nftw, nftw, GLIBC_2_1);\n>  #endif\n>  \n> -/* We define PATH_MAX if the system does not provide a definition.\n> -   This does not artificially limit any operation.  PATH_MAX is simply\n> -   used as a guesstimate for the expected maximal path length.\n> -   Buffers will be enlarged if necessary.  */\n> -#ifndef PATH_MAX\n> -# define PATH_MAX 1024\n>  #endif\n> -\n> -struct dir_data\n> -{\n> -  DIR *stream;\n> -  int streamfd;\n> -  char *content;\n> -};\n> -\n> -struct known_object\n> -{\n> -  dev_t dev;\n> -  INO_T ino;\n> -};\n> -\n> -/* Represents the execution state of a directory processing frame within the\n> -   iterative file tree walk loop.\n> -\n> -   Because the tree traversal is implemented iteratively using a custom stack\n> -   rather than standard recursion, this state machine tracks the progress\n> -   of each directory currently being visited.  */\n> -enum ftw_frame_state\n> -{\n> -  /* The initial state of a newly pushed directory frame.  Attempts to open\n> -     the directory stream.  If successful, transitions to\n> -     FTW_STATE_STREAM_LOOP.  */\n> -  FTW_STATE_INIT = 0,\n> -\n> -  /* Iterating over the directory entries directly from the open DIR stream\n> -     (using readdir).  If a subdirectory is encountered and needs to be\n> -     descended into, a new frame is added to the stack and execution pauses\n> -     here.  Transitions to FTW_STATE_CONTENT_LOOP if the stream was closed\n> -     and cached to free up file descriptors, or FTW_STATE_CLEANUP when\n> -     done.  */\n> -  FTW_STATE_STREAM_LOOP,\n> -\n> -  /* Iterating over directory entries from a cached memory buffer.  This state\n> -     is used as a fallback when the original DIR stream had to be closed\n> -     prematurely to prevent file descriptor exhaustion while descending into\n> -     deeply nested child directories.  Transitions to FTW_STATE_CLEANUP when\n> -     all cached entries are processed.  */\n> -  FTW_STATE_CONTENT_LOOP,\n> -\n> -  /* The final state, handles resource deallocation (closing remaining\n> -     streams, freeing cached content buffers), triggering post-traversal\n> -     callbacks (like FTW_DP for FTW_DEPTH walks), and restoring the\n> -     previous working directory if FTW_CHDIR was used.  */\n> -  FTW_STATE_CLEANUP\n> -};\n> -\n> -/* Keep track of visited directories.  */\n> -struct ftw_frame\n> -{\n> -  struct dir_data dir;\n> -  struct STRUCT_STAT st;\n> -  int previous_base;\n> -  char *runp;\n> -  enum ftw_frame_state state;\n> -};\n> -\n> -struct ftw_stack\n> -{\n> -  struct ftw_frame **stack;\n> -  size_t num_blocks;\n> -  ssize_t top;\n> -};\n> -\n> -typedef union\n> -{\n> -  NFTW_FUNC_T nftw_func;\n> -  FTW_FUNC_T ftw_func;\n> -} func_callback_t;\n> -\n> -struct ftw_data\n> -{\n> -  /* Array with pointers to open directory streams.  */\n> -  struct dir_data **dirstreams;\n> -  size_t actdir;\n> -  size_t maxdir;\n> -\n> -  /* Buffer containing name of currently processed object.  */\n> -  char *dirbuf;\n> -  size_t dirbufsize;\n> -\n> -  /* Passed as fourth argument to `nftw' callback.  The `base' member\n> -     tracks the content of the `dirbuf'.  */\n> -  struct FTW ftw;\n> -\n> -  /* Flags passed to `nftw' function.  0 for `ftw'.  */\n> -  int flags;\n> -\n> -  /* Conversion array for flag values.  It is the identity mapping for\n> -     `nftw' calls, otherwise it maps the values to those known by\n> -     `ftw'.  */\n> -  const int *cvt_arr;\n> -\n> -  /* Callback function.  We always use the `nftw' form.  */\n> -  bool is_nftw;\n> -  func_callback_t func;\n> -\n> -  /* Device of starting point.  Needed for FTW_MOUNT.  */\n> -  dev_t dev;\n> -\n> -  /* Data structure for keeping fingerprints of already processed\n> -     object.  This is needed when not using FTW_PHYS.  */\n> -  void *known_objects;\n> -};\n> -#define CALL_FUNC(__ftw_data, __fp, __sb, __f, __ftw)                            \\\n> -  ((__ftw_data)->is_nftw ? (__ftw_data)->func.nftw_func (__fp, __sb, __f, __ftw) \\\n> -                         : (__ftw_data)->func.ftw_func (__fp, __sb, __f))\n> -\n> -static bool\n> -ftw_allocate (struct ftw_data *data, size_t newsize)\n> -{\n> -  void *newp = realloc (data->dirstreams, data->maxdir\n> -\t\t\t\t\t  * sizeof (struct dir_data *)\n> -\t\t\t\t\t  + newsize);\n> -  if (newp == NULL)\n> -    return false;\n> -  data->dirstreams = newp;\n> -  data->dirbufsize = newsize;\n> -  data->dirbuf = (char *) data->dirstreams\n> -\t\t + data->maxdir * sizeof (struct dir_data *);\n> -  return true;\n> -}\n> -\n> -/* Internally we use the FTW_* constants used for `nftw'.  When invoked\n> -   as `ftw', map each flag to the subset of values used by `ftw'.  */\n> -static const int nftw_arr[] =\n> -{\n> -  FTW_F, FTW_D, FTW_DNR, FTW_NS, FTW_SL, FTW_DP, FTW_SLN\n> -};\n> -\n> -static const int ftw_arr[] =\n> -{\n> -  FTW_F, FTW_D, FTW_DNR, FTW_NS, FTW_F, FTW_D, FTW_NS\n> -};\n> -\n> -\n> -static int\n> -object_compare (const void *p1, const void *p2)\n> -{\n> -  /* We don't need a sophisticated and useful comparison.  We are only\n> -     interested in equality.  However, we must be careful not to\n> -     accidentally compare `holes' in the structure.  */\n> -  const struct known_object *kp1 = p1, *kp2 = p2;\n> -  int cmp1;\n> -  cmp1 = (kp1->ino > kp2->ino) - (kp1->ino < kp2->ino);\n> -  if (cmp1 != 0)\n> -    return cmp1;\n> -  return (kp1->dev > kp2->dev) - (kp1->dev < kp2->dev);\n> -}\n> -\n> -\n> -static int\n> -add_object (struct ftw_data *data, struct STRUCT_STAT *st)\n> -{\n> -  struct known_object *newp = malloc (sizeof (struct known_object));\n> -  if (newp == NULL)\n> -    return -1;\n> -  newp->dev = st->st_dev;\n> -  newp->ino = st->st_ino;\n> -  return __tsearch (newp, &data->known_objects, object_compare) ? 0 : -1;\n> -}\n> -\n> -\n> -static inline int\n> -find_object (struct ftw_data *data, struct STRUCT_STAT *st)\n> -{\n> -  struct known_object obj;\n> -  obj.dev = st->st_dev;\n> -  obj.ino = st->st_ino;\n> -  return __tfind (&obj, &data->known_objects, object_compare) != NULL;\n> -}\n> -\n> -\n> -static inline int\n> -open_dir_stream (int *dfdp, struct ftw_data *data, struct dir_data *dirp)\n> -{\n> -  int result = 0;\n> -\n> -  if (data->dirstreams[data->actdir] != NULL)\n> -    {\n> -      /* Oh, oh.  We must close this stream.  Get all remaining\n> -\t entries and store them as a list in the `content' member of\n> -\t the `struct dir_data' variable.  */\n> -      size_t bufsize = 1024;\n> -      char *buf = malloc (bufsize);\n> -\n> -      if (buf == NULL)\n> -\tresult = -1;\n> -      else\n> -\t{\n> -\t  DIR *st = data->dirstreams[data->actdir]->stream;\n> -\t  struct dirent64 *d;\n> -\t  size_t actsize = 0;\n> -\n> -\t  while ((d = __readdir64 (st)) != NULL)\n> -\t    {\n> -\t      size_t this_len = NAMLEN (d);\n> -\t      if (actsize + this_len + 2 >= bufsize)\n> -\t\t{\n> -\t\t  char *newp;\n> -\t\t  bufsize += MAX (1024, 2 * this_len);\n> -\t\t  newp = (char *) realloc (buf, bufsize);\n> -\t\t  if (newp == NULL)\n> -\t\t    {\n> -\t\t      /* No more memory.  */\n> -\t\t      free (buf);\n> -\t\t      return -1;\n> -\t\t    }\n> -\t\t  buf = newp;\n> -\t\t}\n> -\n> -\t      *((char *) __mempcpy (buf + actsize, d->d_name, this_len))\n> -\t\t= '\\0';\n> -\t      actsize += this_len + 1;\n> -\t    }\n> -\n> -\t  /* Terminate the list with an additional NUL byte.  */\n> -\t  buf[actsize++] = '\\0';\n> -\n> -\t  /* Shrink the buffer to what we actually need.  */\n> -\t  void *content = realloc (buf, actsize);\n> -\t  data->dirstreams[data->actdir]->content = content;\n> -\t  if (content == NULL)\n> -\t    {\n> -\t      free (buf);\n> -\t      result = -1;\n> -\t    }\n> -\t  else\n> -\t    {\n> -\t      __closedir (st);\n> -\t      data->dirstreams[data->actdir]->stream = NULL;\n> -\t      data->dirstreams[data->actdir]->streamfd = -1;\n> -\t      data->dirstreams[data->actdir] = NULL;\n> -\t    }\n> -\t}\n> -    }\n> -\n> -  /* Open the new stream.  */\n> -  if (result == 0)\n> -    {\n> -      assert (data->dirstreams[data->actdir] == NULL);\n> -\n> -      if (dfdp != NULL && *dfdp != -1)\n> -\t{\n> -\t  int fd = __openat64_nocancel (*dfdp, data->dirbuf + data->ftw.base,\n> -\t\t\t\t\tO_RDONLY | O_DIRECTORY | O_NDELAY);\n> -\t  dirp->stream = NULL;\n> -\t  if (fd != -1 && (dirp->stream = __fdopendir (fd)) == NULL)\n> -\t    __close_nocancel_nostatus (fd);\n> -\t}\n> -      else\n> -\t{\n> -\t  const char *name;\n> -\n> -\t  if (data->flags & FTW_CHDIR)\n> -\t    {\n> -\t      name = data->dirbuf + data->ftw.base;\n> -\t      if (name[0] == '\\0')\n> -\t\tname = \".\";\n> -\t    }\n> -\t  else\n> -\t    name = data->dirbuf;\n> -\n> -\t  dirp->stream = __opendir (name);\n> -\t}\n> -\n> -      if (dirp->stream == NULL)\n> -\tresult = -1;\n> -      else\n> -\t{\n> -\t  dirp->streamfd = __dirfd (dirp->stream);\n> -\t  dirp->content = NULL;\n> -\t  data->dirstreams[data->actdir] = dirp;\n> -\n> -\t  if (++data->actdir == data->maxdir)\n> -\t    data->actdir = 0;\n> -\t}\n> -    }\n> -\n> -  return result;\n> -}\n> -\n> -\n> -static int\n> -process_entry (struct ftw_data *data, struct dir_data *dir, const char *name,\n> -\t       size_t namlen, struct STRUCT_STAT *out_st, bool *descend)\n> -{\n> -  struct STRUCT_STAT st;\n> -  int result = 0;\n> -  int flag = 0;\n> -  size_t new_buflen;\n> -\n> -  *descend = false;\n> -\n> -  if (name[0] == '.' && (name[1] == '\\0'\n> -\t\t\t || (name[1] == '.' && name[2] == '\\0')))\n> -    /* Don't process the \".\" and \"..\" entries.  */\n> -    return 0;\n> -\n> -  new_buflen = data->ftw.base + namlen + 2;\n> -  if (data->dirbufsize < new_buflen\n> -      && !ftw_allocate (data, 2 * new_buflen))\n> -    return -1;\n> -\n> -  *((char *) __mempcpy (data->dirbuf + data->ftw.base, name, namlen)) = '\\0';\n> -\n> -  int statres;\n> -  if (dir->streamfd != -1)\n> -    statres = FSTATAT (dir->streamfd, name, &st,\n> -\t\t       (data->flags & FTW_PHYS) ? AT_SYMLINK_NOFOLLOW : 0);\n> -  else\n> -    {\n> -      if ((data->flags & FTW_CHDIR) == 0)\n> -\tname = data->dirbuf;\n> -\n> -      statres = ((data->flags & FTW_PHYS)\n> -\t\t ? LSTAT (name, &st)\n> -\t\t : STAT (name, &st));\n> -    }\n> -\n> -  if (statres < 0)\n> -    {\n> -      if (errno != EACCES && errno != ENOENT)\n> -\tresult = -1;\n> -      else if (data->flags & FTW_PHYS)\n> -\tflag = FTW_NS;\n> -      else\n> -\t{\n> -\t  /* Old code left ST undefined for dangling DT_LNK without\n> -\t     FTW_PHYS set; a clarification at the POSIX level suggests\n> -\t     it should contain information about the link (ala lstat).\n> -\t     We do our best to fill in what data we can.  */\n> -\t  if (dir->streamfd != -1)\n> -\t    statres = FSTATAT (dir->streamfd, name, &st,\n> -\t\t\t       AT_SYMLINK_NOFOLLOW);\n> -\t  else\n> -\t    statres = LSTAT (name, &st);\n> -\t  if (statres == 0 && S_ISLNK (st.st_mode))\n> -\t    flag = FTW_SLN;\n> -\t  else\n> -\t    flag = FTW_NS;\n> -\t}\n> -    }\n> -  else\n> -    {\n> -      if (S_ISDIR (st.st_mode))\n> -\tflag = FTW_D;\n> -      else if (S_ISLNK (st.st_mode))\n> -\tflag = FTW_SL;\n> -      else\n> -\tflag = FTW_F;\n> -    }\n> -\n> -  if (result == 0\n> -      && (flag == FTW_NS\n> -\t  || !(data->flags & FTW_MOUNT) || st.st_dev == data->dev))\n> -    {\n> -      if (flag == FTW_D)\n> -\t{\n> -\t  if ((data->flags & FTW_PHYS)\n> -\t      || (!find_object (data, &st)\n> -\t\t  /* Remember the object.  */\n> -\t\t  && (result = add_object (data, &st)) == 0))\n> -\t    {\n> -               *out_st = st;\n> -               *descend = true;\n> -\t    }\n> -\t}\n> -      else\n> -\tresult = CALL_FUNC (data, data->dirbuf, &st, data->cvt_arr[flag],\n> -\t\t\t    &data->ftw);\n> -    }\n> -\n> -  if ((data->flags & FTW_ACTIONRETVAL) && result == FTW_SKIP_SUBTREE)\n> -    result = 0;\n> -\n> -  return result;\n> -}\n> -\n> -\n> -/* The ftw_frame are kept as chunked array to minimize the reallocation cost\n> -   when the stack grows (since it contains STRUCT_STAT and extra metadata).\n> -   New chunks of ftw_framw are allocated and only freed when ftw returns.  */\n> -enum\n> -{\n> -  FTW_STACK_CHUNK_BLOCKS  = 1,  /* Number of initial allocated chunks.  */\n> -  FTW_STACK_CHUNK_SIZE    = 32  /* Number of stack frames allocated per\n> -\t\t\t\t   chunk.  */\n> -};\n> -\n> -static inline struct ftw_frame *\n> -frame_stack_get (struct ftw_stack *ftwst, int adj)\n> -{\n> -  return &ftwst->stack[(ftwst->top + adj) / FTW_STACK_CHUNK_SIZE]\n> -    [(ftwst->top + adj) % FTW_STACK_CHUNK_SIZE];\n> -}\n> -\n> -static inline void\n> -frame_stack_reset_top (struct ftw_stack *fwtst, const struct STRUCT_STAT *st)\n> -{\n> -  struct ftw_frame *frame = frame_stack_get (fwtst, 0);\n> -  frame->st = *st;\n> -  frame->state = FTW_STATE_INIT;\n> -  frame->dir.stream = NULL;\n> -  frame->dir.content = NULL;\n> -  frame->dir.streamfd = -1;\n> -}\n> -\n> -static bool\n> -frame_stack_init (struct ftw_stack *ftwst, const struct STRUCT_STAT *st)\n> -{\n> -  ftwst->num_blocks = FTW_STACK_CHUNK_BLOCKS;\n> -  ftwst->stack = malloc (FTW_STACK_CHUNK_BLOCKS * sizeof (*ftwst->stack));\n> -  if (ftwst->stack == NULL)\n> -    return false;\n> -\n> -  ftwst->stack[0] = malloc (FTW_STACK_CHUNK_SIZE * sizeof (struct ftw_frame));\n> -  if (ftwst->stack[0] == NULL)\n> -    {\n> -      free (ftwst->stack);\n> -      return false;\n> -    }\n> -\n> -  ftwst->top = 0;\n> -  frame_stack_reset_top (ftwst, st);\n> -  return true;\n> -}\n> -\n> -static void\n> -frame_stack_free (struct ftw_stack *ftwst)\n> -{\n> -  for (size_t i = 0; i < ftwst->num_blocks; i++)\n> -    free (ftwst->stack[i]);\n> -  free (ftwst->stack);\n> -}\n> -\n> -static bool\n> -frame_stack_add (struct ftw_stack *ftwst, const struct STRUCT_STAT *st)\n> -{\n> -  if (ftwst->top + 1 >= ftwst->num_blocks * FTW_STACK_CHUNK_SIZE)\n> -    {\n> -      size_t new_blocks = ftwst->num_blocks + 1;\n> -      struct ftw_frame **new_stack = realloc (\n> -\t  ftwst->stack, new_blocks * sizeof (*ftwst->stack));\n> -\n> -      if (new_stack == NULL)\n> -\treturn false;\n> -      ftwst->stack = new_stack;\n> -      ftwst->stack[ftwst->num_blocks] = malloc (\n> -\t  FTW_STACK_CHUNK_SIZE * sizeof (struct ftw_frame));\n> -      if (ftwst->stack[ftwst->num_blocks] == NULL)\n> -\treturn false;\n> -      ftwst->num_blocks = new_blocks;\n> -    }\n> -  ftwst->top++;\n> -  frame_stack_reset_top (ftwst, st);\n> -  return true;\n> -}\n> -\n> -static void\n> -frame_closedir (struct ftw_data *data, struct ftw_frame *frame)\n> -{\n> -  int save_err = errno;\n> -  assert (frame->dir.content == NULL);\n> -  __closedir (frame->dir.stream);\n> -  frame->dir.streamfd = -1;\n> -  __set_errno (save_err);\n> -  if (data->actdir-- == 0)\n> -    data->actdir = data->maxdir - 1;\n> -  data->dirstreams[data->actdir] = NULL;\n> -  frame->dir.stream = NULL;\n> -}\n> -\n> -static int\n> -ftw_dir (struct ftw_data *data, const struct STRUCT_STAT *st)\n> -{\n> -  struct ftw_stack ftwst;\n> -  if (!frame_stack_init (&ftwst, st))\n> -    return -1;\n> -\n> -  int result = 0;\n> -\n> -  while (ftwst.top >= 0)\n> -    {\n> -      struct ftw_frame *frame = frame_stack_get (&ftwst, 0);\n> -      struct dir_data *old_dir = (ftwst.top > 0)\n> -\t? &frame_stack_get (&ftwst, -1)->dir : NULL;\n> -\n> -      if (frame->state == FTW_STATE_INIT)\n> -\t{\n> -\t  frame->previous_base = data->ftw.base;\n> -\t  result = open_dir_stream (\n> -\t      old_dir == NULL ? NULL : &old_dir->streamfd, data, &frame->dir);\n> -\t  if (result != 0)\n> -\t    {\n> -\t      if (errno == EACCES)\n> -\t\tresult = CALL_FUNC (data, data->dirbuf, &frame->st, FTW_DNR,\n> -\t\t\t\t    &data->ftw);\n> -\t      ftwst.top--;\n> -\t      /* Intercept FTW_SKIP_SUBTREE when popping frame */\n> -\t      if (ftwst.top >= 0 && (data->flags & FTW_ACTIONRETVAL)\n> -\t\t  && result == FTW_SKIP_SUBTREE)\n> -\t\tresult = 0;\n> -\t      continue;\n> -\t    }\n> -\n> -\t  if (!(data->flags & FTW_DEPTH))\n> -\t    {\n> -\t      result = CALL_FUNC (data, data->dirbuf, &frame->st, FTW_D,\n> -\t\t\t\t  &data->ftw);\n> -\t      if (result != 0)\n> -\t\tgoto state0_fail;\n> -\t    }\n> -\n> -\t  if (data->flags & FTW_CHDIR)\n> -\t    {\n> -\t      if (__fchdir (__dirfd (frame->dir.stream)) < 0)\n> -\t\t{\n> -\t\t  result = -1;\n> -\t\tstate0_fail:\n> -\t\t  frame_closedir (data, frame);\n> -\t\t  ftwst.top--;\n> -\t\t  /* Intercept FTW_SKIP_SUBTREE when popping frame.  */\n> -\t\t  if (ftwst.top >= 0 && (data->flags & FTW_ACTIONRETVAL)\n> -\t\t      && result == FTW_SKIP_SUBTREE)\n> -\t\t    result = 0;\n> -\t\t  continue;\n> -\t\t}\n> -\t    }\n> -\n> -\t  ++data->ftw.level;\n> -\t  char *startp = strchr (data->dirbuf, '\\0');\n> -\t  assert (startp != data->dirbuf);\n> -\t  if (startp[-1] != '/')\n> -\t    *startp++ = '/';\n> -\t  data->ftw.base = startp - data->dirbuf;\n> -\n> -\t  frame->state = FTW_STATE_STREAM_LOOP;\n> -\t  frame->runp = frame->dir.content;\n> -\t}\n> -      else if (frame->state == FTW_STATE_STREAM_LOOP)\n> -\t{\n> -\t  if (result != 0)\n> -\t    {\n> -\t      frame->state = FTW_STATE_CLEANUP;\n> -\t      continue;\n> -\t    }\n> -\n> -\t  if (frame->dir.stream == NULL)\n> -\t    {\n> -\t      frame->state = FTW_STATE_CONTENT_LOOP;\n> -\t      frame->runp = frame->dir.content;\n> -\t      continue;\n> -\t    }\n> -\n> -\t  struct dirent64 *d = __readdir64 (frame->dir.stream);\n> -\t  if (d != NULL)\n> -\t    {\n> -\t      struct STRUCT_STAT child_st;\n> -\t      bool descend = false;\n> -\t      result = process_entry (data, &frame->dir, d->d_name, NAMLEN (d),\n> -\t\t\t\t      &child_st, &descend);\n> -\n> -\t      if (result == 0 && descend)\n> -\t\t{\n> -\t\t  if (!frame_stack_add (&ftwst, &child_st))\n> -\t\t    {\n> -\t\t      result = -1;\n> -\t\t      frame->state = FTW_STATE_CLEANUP;\n> -\t\t    }\n> -\t\t  continue;\n> -\t\t}\n> -\t      else if (result != 0)\n> -\t\t{\n> -\t\t  frame->state = FTW_STATE_CLEANUP;\n> -\t\t  continue;\n> -\t\t}\n> -\t    }\n> -\t  else\n> -\t    frame->state = FTW_STATE_CLEANUP;\n> -\t}\n> -      else if (frame->state == FTW_STATE_CONTENT_LOOP)\n> -\t{\n> -\t  /* Check if we are safely positioned to process the starting path.\n> -\t     The 'result' variable here comes from one of two places:\n> -\n> -\t     1. Initialization: defaults to 0 at the top of ftw_startup.  If\n> -\t        the FTW_CHDIR flag was NOT passed, it remains 0, meaning we\n> -\t\tare good to go.\n> -\n> -\t     2. Directory Change: If FTW_CHDIR WAS passed, 'result' holds the\n> -\t        return value of the preceding __chdir() call (either moving\n> -\t\tto \"/\" or the parsed base directory).\n> -\n> -\t     If 'result' is 0, the setup succeeded (or wasn't needed) and we\n> -\t     can safely stat the initial object.  Othewise, the chdir failed,\n> -\t     so we skip processing and fall through to the cleanup phase.  */\n> -\t  if (result != 0)\n> -\t    {\n> -\t      frame->state = FTW_STATE_CLEANUP;\n> -\t      continue;\n> -\t    }\n> -\n> -\t  if (frame->runp != NULL && *frame->runp != '\\0')\n> -\t    {\n> -\t      char *endp = strchr (frame->runp, '\\0');\n> -\t      struct STRUCT_STAT child_st;\n> -\t      bool descend = false;\n> -\n> -\t      result = process_entry (data, &frame->dir, frame->runp,\n> -\t\t\t\t      endp - frame->runp, &child_st,\n> -\t\t\t\t      &descend);\n> -\t      frame->runp = endp + 1;\n> -\n> -\t      if (result == 0 && descend)\n> -\t\t{\n> -\t\t  if (!frame_stack_add (&ftwst, &child_st))\n> -\t\t    {\n> -\t\t      result = -1;\n> -\t\t      frame->state = FTW_STATE_CLEANUP;\n> -\t\t    }\n> -\t\t  continue;\n> -\t\t}\n> -\t      else if (result != 0)\n> -\t\t{\n> -\t\t  frame->state = FTW_STATE_CLEANUP;\n> -\t\t  continue;\n> -\t\t}\n> -\t    }\n> -\t  else\n> -\t    frame->state = FTW_STATE_CLEANUP;\n> -\t}\n> -      else if (frame->state == FTW_STATE_CLEANUP)\n> -\t{\n> -\t  if (frame->dir.stream != NULL)\n> -\t    frame_closedir (data, frame);\n> -\t  else if (frame->dir.content != NULL)\n> -\t    {\n> -\t      free (frame->dir.content);\n> -\t      frame->dir.content = NULL;\n> -\t    }\n> -\n> -\t  if ((data->flags & FTW_ACTIONRETVAL) && result == FTW_SKIP_SIBLINGS)\n> -\t    result = 0;\n> -\n> -\t  data->dirbuf[data->ftw.base - 1] = '\\0';\n> -\t  --data->ftw.level;\n> -\t  data->ftw.base = frame->previous_base;\n> -\n> -\t  if (result == 0 && (data->flags & FTW_DEPTH))\n> -\t    result\n> -\t\t= CALL_FUNC (data, data->dirbuf, &frame->st, FTW_DP,\n> -\t\t\t     &data->ftw);\n> -\n> -\t  if (old_dir != NULL && (data->flags & FTW_CHDIR)\n> -\t      && (result == 0\n> -\t\t  || ((data->flags & FTW_ACTIONRETVAL)\n> -\t\t      && (result != -1 && result != FTW_STOP))))\n> -\t    {\n> -\t      int done = 0;\n> -\t      if (old_dir->stream != NULL)\n> -\t\tif (__fchdir (__dirfd (old_dir->stream)) == 0)\n> -\t\t  done = 1;\n> -\n> -\t      if (!done)\n> -\t\t{\n> -\t\t  if (data->ftw.base == 1)\n> -\t\t    {\n> -\t\t      if (__chdir (\"/\") < 0)\n> -\t\t\tresult = -1;\n> -\t\t    }\n> -\t\t  else if (__chdir (\"..\") < 0)\n> -\t\t    result = -1;\n> -\t\t}\n> -\t    }\n> -\n> -\t  ftwst.top--;\n> -\t  /* Intercept FTW_SKIP_SUBTREE when popping frame.  */\n> -\t  if (ftwst.top >= 0 && (data->flags & FTW_ACTIONRETVAL)\n> -\t      && result == FTW_SKIP_SUBTREE)\n> -\t    result = 0;\n> -\t}\n> -    }\n> -\n> -  frame_stack_free (&ftwst);\n> -\n> -  return result;\n> -}\n> -\n> -\n> -static int\n> -ftw_startup (const char *dir, bool is_nftw, func_callback_t func,\n> -\t     int descriptors, int flags)\n> -{\n> -  struct ftw_data data = { .dirstreams = NULL };\n> -  struct STRUCT_STAT st;\n> -  int result = 0;\n> -  int cwdfd = -1;\n> -  char *cwd = NULL;\n> -  char *cp;\n> -\n> -  /* First make sure the parameters are reasonable.  */\n> -  if (dir[0] == '\\0')\n> -    {\n> -      __set_errno (ENOENT);\n> -      return -1;\n> -    }\n> -\n> -  data.maxdir = descriptors < 1 ? 1 : descriptors;\n> -  data.actdir = 0;\n> -  /* PATH_MAX is always defined when we get here.  */\n> -  if (!ftw_allocate (&data, MAX (2 * strlen (dir), PATH_MAX)))\n> -    return -1;\n> -  memset (data.dirstreams, '\\0', data.maxdir * sizeof (struct dir_data *));\n> -  cp = __stpcpy (data.dirbuf, dir);\n> -  /* Strip trailing slashes.  */\n> -  while (cp > data.dirbuf + 1 && cp[-1] == '/')\n> -    --cp;\n> -  *cp = '\\0';\n> -\n> -  data.ftw.level = 0;\n> -\n> -  /* Find basename.  */\n> -  while (cp > data.dirbuf && cp[-1] != '/')\n> -    --cp;\n> -  data.ftw.base = cp - data.dirbuf;\n> -\n> -  data.flags = flags;\n> -\n> -  data.is_nftw = is_nftw;\n> -  data.func = func;\n> -\n> -  /* Since we internally use the complete set of FTW_* values we need\n> -     to reduce the value range before calling a `ftw' callback.  */\n> -  data.cvt_arr = is_nftw ? nftw_arr : ftw_arr;\n> -\n> -  /* No object known so far.  */\n> -  data.known_objects = NULL;\n> -\n> -  /* Now go to the directory containing the initial file/directory.  */\n> -  if (flags & FTW_CHDIR)\n> -    {\n> -      /* We have to be able to go back to the current working\n> -\t directory.  The best way to do this is to use a file\n> -\t descriptor.  */\n> -      cwdfd = __open (\".\", O_RDONLY | O_DIRECTORY);\n> -      if (cwdfd == -1)\n> -\t{\n> -\t  /* Try getting the directory name.  This can be needed if\n> -\t     the current directory is executable but not readable.  */\n> -\t  if (errno == EACCES)\n> -\t    /* GNU extension ahead.  */\n> -\t    cwd =  __getcwd (NULL, 0);\n> -\n> -\t  if (cwd == NULL)\n> -\t    goto out_fail;\n> -\t}\n> -      else if (data.maxdir > 1)\n> -\t/* Account for the file descriptor we use here.  */\n> -\t--data.maxdir;\n> -\n> -      if (data.ftw.base > 0)\n> -\t{\n> -\t  /* Change to the directory the file is in.  In data.dirbuf\n> -\t     we have a writable copy of the file name.  Just NUL\n> -\t     terminate it for now and change the directory.  */\n> -\t  if (data.ftw.base == 1)\n> -\t    /* I.e., the file is in the root directory.  */\n> -\t    result = __chdir (\"/\");\n> -\t  else\n> -\t    {\n> -\t      char ch = data.dirbuf[data.ftw.base - 1];\n> -\t      data.dirbuf[data.ftw.base - 1] = '\\0';\n> -\t      result = __chdir (data.dirbuf);\n> -\t      data.dirbuf[data.ftw.base - 1] = ch;\n> -\t    }\n> -\t}\n> -    }\n> -\n> -  /* Get stat info for start directory.  */\n> -  if (result == 0)\n> -    {\n> -      const char *name;\n> -\n> -      if (data.flags & FTW_CHDIR)\n> -\t{\n> -\t  name = data.dirbuf + data.ftw.base;\n> -\t  if (name[0] == '\\0')\n> -\t    name = \".\";\n> -\t}\n> -      else\n> -\tname = data.dirbuf;\n> -\n> -      if (((flags & FTW_PHYS)\n> -\t   ? LSTAT (name, &st)\n> -\t   : STAT (name, &st)) < 0)\n> -\t{\n> -\t  if (!(flags & FTW_PHYS)\n> -\t      && errno == ENOENT\n> -\t      && LSTAT (name, &st) == 0\n> -\t      && S_ISLNK (st.st_mode))\n> -\t    result = CALL_FUNC (&data, data.dirbuf, &st, data.cvt_arr[FTW_SLN],\n> -\t\t\t\t&data.ftw);\n> -\t  else\n> -\t    /* No need to call the callback since we cannot say anything\n> -\t       about the object.  */\n> -\t    result = -1;\n> -\t}\n> -      else\n> -\t{\n> -\t  if (S_ISDIR (st.st_mode))\n> -\t    {\n> -\t      /* Remember the device of the initial directory in case\n> -\t\t FTW_MOUNT is given.  */\n> -\t      data.dev = st.st_dev;\n> -\n> -\t      /* We know this directory now.  */\n> -\t      if (!(flags & FTW_PHYS))\n> -\t\tresult = add_object (&data, &st);\n> -\n> -\t      if (result == 0)\n> -\t\tresult = ftw_dir (&data, &st);\n> -\t    }\n> -\t  else\n> -\t    {\n> -\t      int flag = S_ISLNK (st.st_mode) ? FTW_SL : FTW_F;\n> -\n> -\t      result = CALL_FUNC (&data, data.dirbuf, &st, data.cvt_arr[flag],\n> -\t\t\t\t  &data.ftw);\n> -\t    }\n> -\t}\n> -\n> -      if ((flags & FTW_ACTIONRETVAL)\n> -\t  && (result == FTW_SKIP_SUBTREE || result == FTW_SKIP_SIBLINGS))\n> -\tresult = 0;\n> -    }\n> -\n> -  /* Return to the start directory (if necessary).  */\n> -  if (cwdfd != -1)\n> -    {\n> -      int save_err = errno;\n> -      __fchdir (cwdfd);\n> -      __close_nocancel_nostatus (cwdfd);\n> -      __set_errno (save_err);\n> -    }\n> -  else if (cwd != NULL)\n> -    {\n> -      int save_err = errno;\n> -      __chdir (cwd);\n> -      free (cwd);\n> -      __set_errno (save_err);\n> -    }\n> -\n> -  /* Free all memory.  */\n> - out_fail:\n> -  __tdestroy (data.known_objects, free);\n> -  free (data.dirstreams);\n> -\n> -  return result;\n> -}\n> -\n> -\n> -\n> -/* Entry points.  */\n> -\n> -int\n> -FTW_NAME (const char *path, FTW_FUNC_T func, int descriptors)\n> -{\n> -  return ftw_startup (path, false, (func_callback_t) { .ftw_func = func },\n> -\t\t      descriptors, 0);\n> -}\n> -\n> -#ifndef NFTW_OLD_NAME\n> -int\n> -NFTW_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags)\n> -{\n> -  return ftw_startup (path, true, (func_callback_t) { .nftw_func = func },\n> -\t\t      descriptors, flags);\n> -}\n> -#else\n> -\n> -# include <shlib-compat.h>\n> -\n> -int NFTW_NEW_NAME (const char *, NFTW_FUNC_T, int, int);\n> -\n> -int\n> -NFTW_NEW_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags)\n> -{\n> -  if (flags\n> -      & ~(FTW_PHYS | FTW_MOUNT | FTW_CHDIR | FTW_DEPTH | FTW_ACTIONRETVAL))\n> -    {\n> -      __set_errno (EINVAL);\n> -      return -1;\n> -    }\n> -  return ftw_startup (path, true, (func_callback_t) { .nftw_func = func },\n> -\t\t      descriptors, flags);\n> -}\n> -versioned_symbol (libc, NFTW_NEW_NAME, NFTW_NAME, GLIBC_2_3_3);\n> -\n> -# if SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_3_3)\n> -\n> -/* Older nftw* version just ignored all unknown flags.  */\n> -\n> -int NFTW_OLD_NAME (const char *, NFTW_FUNC_T, int, int);\n> -\n> -int\n> -attribute_compat_text_section\n> -NFTW_OLD_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags)\n> -{\n> -  flags &= (FTW_PHYS | FTW_MOUNT | FTW_CHDIR | FTW_DEPTH);\n> -  return ftw_startup (path, true, (func_callback_t) { .nftw_func = func },\n> -\t\t      descriptors, flags);\n> -}\n> -\n> -compat_symbol (libc, NFTW_OLD_NAME, NFTW_NAME, GLIBC_2_1);\n> -# endif\n> -#endif /* NFTW_OLD_NAME  */\n> diff --git a/io/ftw64-time64.c b/io/ftw64-time64.c\n> index 2df871f802..88f58fd85c 100644\n> --- a/io/ftw64-time64.c\n> +++ b/io/ftw64-time64.c\n> @@ -29,5 +29,5 @@\n>  # define FTW_FUNC_T     __ftw64_time64_func_t\n>  # define NFTW_FUNC_T    __nftw64_time64_func_t\n>  \n> -# include \"ftw.c\"\n> +# include \"ftw-common.c\"\n>  #endif\n> diff --git a/io/ftw64.c b/io/ftw64.c\n> index 0d7cb30091..d3cd14c21a 100644\n> --- a/io/ftw64.c\n> +++ b/io/ftw64.c\n> @@ -16,8 +16,8 @@\n>     License along with the GNU C Library; if not, see\n>     <https://www.gnu.org/licenses/>.  */\n>  \n> -#define FTW_NAME ftw64\n> -#define NFTW_NAME nftw64\n> +#define FTW_NAME __ftw64\n> +#define NFTW_NAME __nftw64\n>  #define NFTW_OLD_NAME __old_nftw64\n>  #define NFTW_NEW_NAME __new_nftw64\n>  #define INO_T ino64_t\n> @@ -28,4 +28,26 @@\n>  #define FTW_FUNC_T __ftw64_func_t\n>  #define NFTW_FUNC_T __nftw64_func_t\n>  \n> -#include \"ftw.c\"\n> +#define ftw __rename_ftw\n> +#define nftw __rename_nftw\n> +\n> +#include <shlib-compat.h>\n> +#include \"ftw-common.c\"\n> +\n> +#undef ftw\n> +#undef nftw\n> +\n> +weak_alias (__ftw64, ftw64)\n> +versioned_symbol (libc, __new_nftw64, nftw64, GLIBC_2_3_3);\n> +\n> +#if SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_3_3)\n> +compat_symbol (libc, __old_nftw64, nftw64, GLIBC_2_1);\n> +#endif\n> +\n> +#ifdef __OFF_T_MATCHES_OFF64_T\n> +weak_alias (__ftw64, ftw)\n> +versioned_symbol (libc, __new_nftw64, nftw, GLIBC_2_3_3);\n> +# if SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_3_3)\n> +compat_symbol (libc, __old_nftw64, nftw, GLIBC_2_1);\n> +# endif\n> +#endif\n> diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw.c b/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw.c\n> deleted file mode 100644\n> index 46389568b2..0000000000\n> --- a/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw.c\n> +++ /dev/null\n> @@ -1 +0,0 @@\n> -#include <io/ftw.c>\n> diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw64.c b/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw64.c\n> deleted file mode 100644\n> index cb02172b3e..0000000000\n> --- a/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw64.c\n> +++ /dev/null\n> @@ -1 +0,0 @@\n> -#include <io/ftw64.c>\n> diff --git a/sysdeps/unix/sysv/linux/x86_64/x32/ftw.c b/sysdeps/unix/sysv/linux/x86_64/x32/ftw.c\n> deleted file mode 100644\n> index a21dfe5690..0000000000\n> --- a/sysdeps/unix/sysv/linux/x86_64/x32/ftw.c\n> +++ /dev/null\n> @@ -1 +0,0 @@\n> -#include <sysdeps/wordsize-64/ftw.c>\n> diff --git a/sysdeps/unix/sysv/linux/x86_64/x32/ftw64.c b/sysdeps/unix/sysv/linux/x86_64/x32/ftw64.c\n> deleted file mode 100644\n> index 3c025b738a..0000000000\n> --- a/sysdeps/unix/sysv/linux/x86_64/x32/ftw64.c\n> +++ /dev/null\n> @@ -1 +0,0 @@\n> -#include <sysdeps/wordsize-64/ftw64.c>\n> diff --git a/sysdeps/wordsize-64/ftw.c b/sysdeps/wordsize-64/ftw.c\n> deleted file mode 100644\n> index ca19903799..0000000000\n> --- a/sysdeps/wordsize-64/ftw.c\n> +++ /dev/null\n> @@ -1,16 +0,0 @@\n> -#define ftw64 __rename_ftw64\n> -#define nftw64 __rename_nftw64\n> -\n> -#include \"../../io/ftw.c\"\n> -\n> -#undef ftw64\n> -#undef nftw64\n> -\n> -weak_alias (ftw, ftw64)\n> -strong_alias (__new_nftw, __new_nftw64)\n> -versioned_symbol (libc, __new_nftw64, nftw64, GLIBC_2_3_3);\n> -\n> -#if SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_3_3)\n> -strong_alias (__old_nftw, __old_nftw64)\n> -compat_symbol (libc, __old_nftw64, nftw64, GLIBC_2_1);\n> -#endif\n> diff --git a/sysdeps/wordsize-64/ftw64.c b/sysdeps/wordsize-64/ftw64.c\n> deleted file mode 100644\n> index 1cfcaadfd1..0000000000\n> --- a/sysdeps/wordsize-64/ftw64.c\n> +++ /dev/null\n> @@ -1 +0,0 @@\n> -/* Defined in ftw.c.  */","headers":{"Return-Path":"<libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org>","X-Original-To":["incoming@patchwork.ozlabs.org","libc-alpha@sourceware.org"],"Delivered-To":["patchwork-incoming@legolas.ozlabs.org","libc-alpha@sourceware.org"],"Authentication-Results":["legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256\n header.s=google header.b=Cb8uaP3a;\n\tdkim-atps=neutral","legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=sourceware.org\n (client-ip=2620:52:6:3111::32; helo=vm01.sourceware.org;\n envelope-from=libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org;\n receiver=patchwork.ozlabs.org)","sourceware.org;\n\tdkim=pass (2048-bit key,\n unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256\n header.s=google header.b=Cb8uaP3a","sourceware.org;\n dmarc=pass (p=none dis=none) header.from=linaro.org","sourceware.org; spf=pass smtp.mailfrom=linaro.org","server2.sourceware.org;\n arc=none smtp.remote-ip=2607:f8b0:4864:20::1329"],"Received":["from vm01.sourceware.org (vm01.sourceware.org\n [IPv6:2620:52:6:3111::32])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4fy29D2J0Wz1yHp\n\tfor <incoming@patchwork.ozlabs.org>; Sat, 18 Apr 2026 03:36:00 +1000 (AEST)","from vm01.sourceware.org (localhost [127.0.0.1])\n\tby sourceware.org (Postfix) with ESMTP id 72DBF4CD2006\n\tfor <incoming@patchwork.ozlabs.org>; Fri, 17 Apr 2026 17:35:58 +0000 (GMT)","from mail-dy1-x1329.google.com (mail-dy1-x1329.google.com\n [IPv6:2607:f8b0:4864:20::1329])\n by sourceware.org (Postfix) with ESMTPS id 078854D108C8\n for <libc-alpha@sourceware.org>; Fri, 17 Apr 2026 17:31:22 +0000 (GMT)","by mail-dy1-x1329.google.com with SMTP id\n 5a478bee46e88-2bd9a485bd6so1791076eec.1\n for <libc-alpha@sourceware.org>; Fri, 17 Apr 2026 10:31:21 -0700 (PDT)","from ?IPV6:2804:1b3:a7c3:d5d0:f8f7:3cdf:3cb1:d627?\n ([2804:1b3:a7c3:d5d0:f8f7:3cdf:3cb1:d627])\n by smtp.gmail.com with ESMTPSA id\n 5a478bee46e88-2e539fa5c38sm2985015eec.5.2026.04.17.10.31.17\n (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128);\n Fri, 17 Apr 2026 10:31:18 -0700 (PDT)"],"DKIM-Filter":["OpenDKIM Filter v2.11.0 sourceware.org 72DBF4CD2006","OpenDKIM Filter v2.11.0 sourceware.org 078854D108C8"],"DMARC-Filter":"OpenDMARC Filter v1.4.2 sourceware.org 078854D108C8","ARC-Filter":"OpenARC Filter v1.0.0 sourceware.org 078854D108C8","ARC-Seal":"i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1776447082; cv=none;\n b=Ub3ztLb+x7p7aO1QJ9700OJyomYrPgFcktPaAcJIVasfdMm/VloD62E2zXypBvnaKzwHzempXnWpiS75+g2T5fvZne7ixpvZhezkIyUvLx07K/A2MALhuGHErA0wrjWXs2J21FnylxIYrjTBC8XHNdnZREGXG/YwLcUg06nvFho=","ARC-Message-Signature":"i=1; a=rsa-sha256; d=sourceware.org; s=key;\n t=1776447082; c=relaxed/simple;\n bh=k2aSO14afYzoB42lEnzKH9S9BecKDQesL971gtgOAoo=;\n h=DKIM-Signature:Message-ID:Date:MIME-Version:Subject:From:To;\n b=hjqhzBsA28Ze58fIdJ1FKUqCc/JtRk59+iWUl5wni9/Tet+EpfpvRc+FBQ1kYzaeK/5SmFR/Yb0sFkkIJCZ5/ScJNIQ2KJf4NY9Hijm8p8lf4P2SF2EXvlbzMpqqMsucdbVLB0h49Jcd/mXVYXhrJWaHIQ5o+jT/pQo6L10zrRg=","ARC-Authentication-Results":"i=1; server2.sourceware.org","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=linaro.org; s=google; t=1776447081; x=1777051881; darn=sourceware.org;\n h=content-transfer-encoding:in-reply-to:organization:content-language\n :references:cc:to:from:subject:user-agent:mime-version:date\n :message-id:from:to:cc:subject:date:message-id:reply-to;\n bh=4JSKen5m97v1ucNVLmnvcjz2/eXqQq8djcECobj0KZY=;\n b=Cb8uaP3aYt35IO6XD7J00scVZChCRNOQRiV5RFMQrihx7ZXr38+MYb5J2GCc4doZeQ\n ppcTsFHfdY2dHeh8aamDeMXkmI1JseZcTveEFgYEi/gmsDFlvTOgo9oW0N+ql1XuFCYI\n jIB0fEPCxr2Gov7h88JcU9cVAylf+A+FgoRt1WpQfrBaje5Yd7xVSMwwWSRfkiHzOqXl\n 0oBwF7DNq2TTXBChSlV85iq7MvI6g68X+6J5PvdHY5HE+nR8EEhef5VU23Web4dQqQx5\n ynxTNEMuOkIwYQGiTG/fD7KFab17Cz5PlIYRyTuOlHnp4O8C1EwN5GNlucame9PsNjtR\n Hgew==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1776447081; x=1777051881;\n h=content-transfer-encoding:in-reply-to:organization:content-language\n :references:cc:to:from:subject:user-agent:mime-version:date\n :message-id:x-gm-gg:x-gm-message-state:from:to:cc:subject:date\n :message-id:reply-to;\n bh=4JSKen5m97v1ucNVLmnvcjz2/eXqQq8djcECobj0KZY=;\n b=VdTzlscGQwjUSHb4IsUs+pDnkHdA6G6ENdZMH1l5YKBaAjwxULutHIx5vOtOu9PN7B\n h+zHVOCFpM/Wn8I0zOx2D1vIrhqlb/WjO1CZwd77MaCfKInrIfq1r7N/HgopkGD5n2Tj\n 5g3v9w0Vz4ZuHnSW+mRu4CDKz83R597k4Faz5dSqPWKQnaq6vleoUswqky6g8K/ZtSoc\n DWRh6rf7TsgBhGETCNKTJHLnYS4+0KnvJ0ab0BgLomQ4M/13cKiFund+zRRI+EvcWVIQ\n isk2nuwFGfegGNaMIjs0P9Nx1HwKqDdiVXaZoQu35JdRGtTbPlIyQARBxu8fcfVJUnJ2\n x/Jw==","X-Gm-Message-State":"AOJu0Yyw8qluD1Ct+OOvw2lBUzVz//gygvKXRzYXBnkOmFLShYCg2C6A\n 7UXVp2BJxd0Kw34Js84bAfGB2yN7/v40WHoQUoN21weuv8pUzg77+2D+L/0cZdNNqlrXa9eLjW5\n jTrHr","X-Gm-Gg":"AeBDiev/7LLtQ/aeh4SHPsCMugqp5C5fohRF+bfzyREMZEJhJfTYQZWGjhpw8CoxCz2\n xlmpJa3J+0OBUo2zMabvFhQA6CwZNACgIcgS7zjdItXCqUJpc7HHeqeeT670IFJ60Io9aoJvcpe\n lzvDWo0gu58BsnHth43TuNpiyprQz+P2skQnng7b0cE/nq2az2pDIJ0FSdn3J/f8QLyv/My63ig\n wK/n1+jxRkacCRv703rpZa/0mVy5FlhoXR4RTHJYPI9PFureRd051Sdqkcc4uLUR7c/oqWuOmT1\n DBZ6Zcv3FfavnioZjyRwdPBlGYoXCYgppz1rTbioXqjl4sXLQWxWjfgEcU93VyHuCI2UGrKjs4z\n NtnnLricP8Tf9OnBFtpyviI3aQuCTLI4AIOGKyTHC3MxSjvVX1akzaxCWZlCC5qL74I7IWKBlcO\n 6YaQm5TR88B/4HHMl1nWq9pX0H0Um+4T19tvrPGhugJZewmtW/ibvvgTCV+KIsz91+TNQ8NNbe+\n IR8Ly9C1wcGXHCFf9cjKbkGGyHCzgWLASLQ9fcVHNfL","X-Received":"by 2002:a05:7300:7fa5:b0:2d9:ad46:4a92 with SMTP id\n 5a478bee46e88-2e478a2f27amr1963180eec.13.1776447079684;\n Fri, 17 Apr 2026 10:31:19 -0700 (PDT)","Message-ID":"<3aa40b5a-25c2-4640-aef2-17722fe59154@linaro.org>","Date":"Fri, 17 Apr 2026 14:31:16 -0300","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH 2/3] io: Consolidate ftw implementation","From":"Adhemerval Zanella Netto <adhemerval.zanella@linaro.org>","To":"libc-alpha@sourceware.org","Cc":"Collin Funk <collin.funk1@gmail.com>, Paul Eggert <eggert@cs.ucla.edu>","References":"<20260417132808.235562-1-adhemerval.zanella@linaro.org>\n <20260417132808.235562-3-adhemerval.zanella@linaro.org>","Content-Language":"en-US","Organization":"Linaro","In-Reply-To":"<20260417132808.235562-3-adhemerval.zanella@linaro.org>","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"7bit","X-BeenThere":"libc-alpha@sourceware.org","X-Mailman-Version":"2.1.30","Precedence":"list","List-Id":"Libc-alpha mailing list <libc-alpha.sourceware.org>","List-Unsubscribe":"<https://sourceware.org/mailman/options/libc-alpha>,\n <mailto:libc-alpha-request@sourceware.org?subject=unsubscribe>","List-Archive":"<https://sourceware.org/pipermail/libc-alpha/>","List-Post":"<mailto:libc-alpha@sourceware.org>","List-Help":"<mailto:libc-alpha-request@sourceware.org?subject=help>","List-Subscribe":"<https://sourceware.org/mailman/listinfo/libc-alpha>,\n <mailto:libc-alpha-request@sourceware.org?subject=subscribe>","Errors-To":"libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org"}},{"id":3678877,"web_url":"http://patchwork.ozlabs.org/comment/3678877/","msgid":"<a7163076-2196-4180-bbef-da7bc94c119e@redhat.com>","list_archive_url":null,"date":"2026-04-17T20:10:04","subject":"Re: [PATCH 2/3] io: Consolidate ftw implementation","submitter":{"id":22438,"url":"http://patchwork.ozlabs.org/api/people/22438/","name":"Carlos O'Donell","email":"carlos@redhat.com"},"content":"On 4/17/26 1:31 PM, Adhemerval Zanella Netto wrote:\n> This is a mechanical patch and I install if no one opposes it.\n> \n\nLikewise. Please push.\n\n> On 17/04/26 10:24, Adhemerval Zanella wrote:\n>> Remove wordsize-64 and arch-specific implementations, for ABIs when\n>> off_t is the same as off64_t (__OFF_T_MATCHES_OFF64_T) the ftw64.c\n>> will create the requires aliases.\n>>\n>> The ftw.c implementation is moved to ftw-common.c to simplify\n>> the __OFF_T_MATCHES_OFF64_T usage.\n>> ---\n>>   io/ftw-common.c                               | 969 ++++++++++++++++++\n>>   io/ftw.c                                      | 958 +----------------\n>>   io/ftw64-time64.c                             |   2 +-\n>>   io/ftw64.c                                    |  28 +-\n>>   sysdeps/unix/sysv/linux/mips/mips64/n64/ftw.c |   1 -\n>>   .../unix/sysv/linux/mips/mips64/n64/ftw64.c   |   1 -\n>>   sysdeps/unix/sysv/linux/x86_64/x32/ftw.c      |   1 -\n>>   sysdeps/unix/sysv/linux/x86_64/x32/ftw64.c    |   1 -\n>>   sysdeps/wordsize-64/ftw.c                     |  16 -\n>>   sysdeps/wordsize-64/ftw64.c                   |   1 -\n>>   10 files changed, 1003 insertions(+), 975 deletions(-)\n>>   create mode 100644 io/ftw-common.c\n>>   delete mode 100644 sysdeps/unix/sysv/linux/mips/mips64/n64/ftw.c\n>>   delete mode 100644 sysdeps/unix/sysv/linux/mips/mips64/n64/ftw64.c\n>>   delete mode 100644 sysdeps/unix/sysv/linux/x86_64/x32/ftw.c\n>>   delete mode 100644 sysdeps/unix/sysv/linux/x86_64/x32/ftw64.c\n>>   delete mode 100644 sysdeps/wordsize-64/ftw.c\n>>   delete mode 100644 sysdeps/wordsize-64/ftw64.c\n>>\n>> diff --git a/io/ftw-common.c b/io/ftw-common.c\n>> new file mode 100644\n>> index 0000000000..07df0ab25a\n>> --- /dev/null\n>> +++ b/io/ftw-common.c\n>> @@ -0,0 +1,969 @@\n>> +/* File tree walker functions.\n>> +   Copyright (C) 1996-2026 Free Software Foundation, Inc.\n>> +   This file is part of the GNU C Library.\n>> +\n>> +   The GNU C Library is free software; you can redistribute it and/or\n>> +   modify it under the terms of the GNU Lesser General Public\n>> +   License as published by the Free Software Foundation; either\n>> +   version 2.1 of the License, or (at your option) any later version.\n>> +\n>> +   The GNU C Library is distributed in the hope that it will be useful,\n>> +   but WITHOUT ANY WARRANTY; without even the implied warranty of\n>> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n>> +   Lesser General Public License for more details.\n>> +\n>> +   You should have received a copy of the GNU Lesser General Public\n>> +   License along with the GNU C Library; if not, see\n>> +   <https://www.gnu.org/licenses/>.  */\n>> +\n>> +\n>> +#include <assert.h>\n>> +#include <dirent.h>\n>> +#include <fcntl.h>\n>> +#include <ftw.h>\n>> +#include <not-cancel.h>\n>> +#include <search.h>\n>> +#include <unistd.h>\n>> +#include <sys/param.h>\n>> +\n>> +#define NAMLEN(dirent) _D_EXACT_NAMLEN (dirent)\n>> +\n>> +/* Support for the LFS API version.  */\n>> +#ifndef FTW_NAME\n>> +# define FTW_NAME ftw\n>> +# define NFTW_NAME nftw\n>> +# define NFTW_OLD_NAME __old_nftw\n>> +# define NFTW_NEW_NAME __new_nftw\n>> +# define INO_T ino_t\n>> +# define STRUCT_STAT stat\n>> +# define LSTAT __lstat\n>> +# define STAT __stat\n>> +# define FSTATAT __fstatat\n>> +# define FTW_FUNC_T __ftw_func_t\n>> +# define NFTW_FUNC_T __nftw_func_t\n>> +#endif\n>> +\n>> +/* We define PATH_MAX if the system does not provide a definition.\n>> +   This does not artificially limit any operation.  PATH_MAX is simply\n>> +   used as a guesstimate for the expected maximal path length.\n>> +   Buffers will be enlarged if necessary.  */\n>> +#ifndef PATH_MAX\n>> +# define PATH_MAX 1024\n>> +#endif\n>> +\n>> +struct dir_data\n>> +{\n>> +  DIR *stream;\n>> +  int streamfd;\n>> +  char *content;\n>> +};\n>> +\n>> +struct known_object\n>> +{\n>> +  dev_t dev;\n>> +  INO_T ino;\n>> +};\n>> +\n>> +/* Represents the execution state of a directory processing frame within the\n>> +   iterative file tree walk loop.\n>> +\n>> +   Because the tree traversal is implemented iteratively using a custom stack\n>> +   rather than standard recursion, this state machine tracks the progress\n>> +   of each directory currently being visited.  */\n>> +enum ftw_frame_state\n>> +{\n>> +  /* The initial state of a newly pushed directory frame.  Attempts to open\n>> +     the directory stream.  If successful, transitions to\n>> +     FTW_STATE_STREAM_LOOP.  */\n>> +  FTW_STATE_INIT = 0,\n>> +\n>> +  /* Iterating over the directory entries directly from the open DIR stream\n>> +     (using readdir).  If a subdirectory is encountered and needs to be\n>> +     descended into, a new frame is added to the stack and execution pauses\n>> +     here.  Transitions to FTW_STATE_CONTENT_LOOP if the stream was closed\n>> +     and cached to free up file descriptors, or FTW_STATE_CLEANUP when\n>> +     done.  */\n>> +  FTW_STATE_STREAM_LOOP,\n>> +\n>> +  /* Iterating over directory entries from a cached memory buffer.  This state\n>> +     is used as a fallback when the original DIR stream had to be closed\n>> +     prematurely to prevent file descriptor exhaustion while descending into\n>> +     deeply nested child directories.  Transitions to FTW_STATE_CLEANUP when\n>> +     all cached entries are processed.  */\n>> +  FTW_STATE_CONTENT_LOOP,\n>> +\n>> +  /* The final state, handles resource deallocation (closing remaining\n>> +     streams, freeing cached content buffers), triggering post-traversal\n>> +     callbacks (like FTW_DP for FTW_DEPTH walks), and restoring the\n>> +     previous working directory if FTW_CHDIR was used.  */\n>> +  FTW_STATE_CLEANUP\n>> +};\n>> +\n>> +/* Keep track of visited directories.  */\n>> +struct ftw_frame\n>> +{\n>> +  struct dir_data dir;\n>> +  struct STRUCT_STAT st;\n>> +  int previous_base;\n>> +  char *runp;\n>> +  enum ftw_frame_state state;\n>> +};\n>> +\n>> +struct ftw_stack\n>> +{\n>> +  struct ftw_frame **stack;\n>> +  size_t num_blocks;\n>> +  ssize_t top;\n>> +};\n>> +\n>> +typedef union\n>> +{\n>> +  NFTW_FUNC_T nftw_func;\n>> +  FTW_FUNC_T ftw_func;\n>> +} func_callback_t;\n>> +\n>> +struct ftw_data\n>> +{\n>> +  /* Array with pointers to open directory streams.  */\n>> +  struct dir_data **dirstreams;\n>> +  size_t actdir;\n>> +  size_t maxdir;\n>> +\n>> +  /* Buffer containing name of currently processed object.  */\n>> +  char *dirbuf;\n>> +  size_t dirbufsize;\n>> +\n>> +  /* Passed as fourth argument to `nftw' callback.  The `base' member\n>> +     tracks the content of the `dirbuf'.  */\n>> +  struct FTW ftw;\n>> +\n>> +  /* Flags passed to `nftw' function.  0 for `ftw'.  */\n>> +  int flags;\n>> +\n>> +  /* Conversion array for flag values.  It is the identity mapping for\n>> +     `nftw' calls, otherwise it maps the values to those known by\n>> +     `ftw'.  */\n>> +  const int *cvt_arr;\n>> +\n>> +  /* Callback function.  We always use the `nftw' form.  */\n>> +  bool is_nftw;\n>> +  func_callback_t func;\n>> +\n>> +  /* Device of starting point.  Needed for FTW_MOUNT.  */\n>> +  dev_t dev;\n>> +\n>> +  /* Data structure for keeping fingerprints of already processed\n>> +     object.  This is needed when not using FTW_PHYS.  */\n>> +  void *known_objects;\n>> +};\n>> +#define CALL_FUNC(__ftw_data, __fp, __sb, __f, __ftw)                            \\\n>> +  ((__ftw_data)->is_nftw ? (__ftw_data)->func.nftw_func (__fp, __sb, __f, __ftw) \\\n>> +                         : (__ftw_data)->func.ftw_func (__fp, __sb, __f))\n>> +\n>> +static bool\n>> +ftw_allocate (struct ftw_data *data, size_t newsize)\n>> +{\n>> +  void *newp = realloc (data->dirstreams, data->maxdir\n>> +\t\t\t\t\t  * sizeof (struct dir_data *)\n>> +\t\t\t\t\t  + newsize);\n>> +  if (newp == NULL)\n>> +    return false;\n>> +  data->dirstreams = newp;\n>> +  data->dirbufsize = newsize;\n>> +  data->dirbuf = (char *) data->dirstreams\n>> +\t\t + data->maxdir * sizeof (struct dir_data *);\n>> +  return true;\n>> +}\n>> +\n>> +/* Internally we use the FTW_* constants used for `nftw'.  When invoked\n>> +   as `ftw', map each flag to the subset of values used by `ftw'.  */\n>> +static const int nftw_arr[] =\n>> +{\n>> +  FTW_F, FTW_D, FTW_DNR, FTW_NS, FTW_SL, FTW_DP, FTW_SLN\n>> +};\n>> +\n>> +static const int ftw_arr[] =\n>> +{\n>> +  FTW_F, FTW_D, FTW_DNR, FTW_NS, FTW_F, FTW_D, FTW_NS\n>> +};\n>> +\n>> +\n>> +static int\n>> +object_compare (const void *p1, const void *p2)\n>> +{\n>> +  /* We don't need a sophisticated and useful comparison.  We are only\n>> +     interested in equality.  However, we must be careful not to\n>> +     accidentally compare `holes' in the structure.  */\n>> +  const struct known_object *kp1 = p1, *kp2 = p2;\n>> +  int cmp1;\n>> +  cmp1 = (kp1->ino > kp2->ino) - (kp1->ino < kp2->ino);\n>> +  if (cmp1 != 0)\n>> +    return cmp1;\n>> +  return (kp1->dev > kp2->dev) - (kp1->dev < kp2->dev);\n>> +}\n>> +\n>> +\n>> +static int\n>> +add_object (struct ftw_data *data, struct STRUCT_STAT *st)\n>> +{\n>> +  struct known_object *newp = malloc (sizeof (struct known_object));\n>> +  if (newp == NULL)\n>> +    return -1;\n>> +  newp->dev = st->st_dev;\n>> +  newp->ino = st->st_ino;\n>> +  return __tsearch (newp, &data->known_objects, object_compare) ? 0 : -1;\n>> +}\n>> +\n>> +\n>> +static inline int\n>> +find_object (struct ftw_data *data, struct STRUCT_STAT *st)\n>> +{\n>> +  struct known_object obj;\n>> +  obj.dev = st->st_dev;\n>> +  obj.ino = st->st_ino;\n>> +  return __tfind (&obj, &data->known_objects, object_compare) != NULL;\n>> +}\n>> +\n>> +\n>> +static inline int\n>> +open_dir_stream (int *dfdp, struct ftw_data *data, struct dir_data *dirp)\n>> +{\n>> +  int result = 0;\n>> +\n>> +  if (data->dirstreams[data->actdir] != NULL)\n>> +    {\n>> +      /* Oh, oh.  We must close this stream.  Get all remaining\n>> +\t entries and store them as a list in the `content' member of\n>> +\t the `struct dir_data' variable.  */\n>> +      size_t bufsize = 1024;\n>> +      char *buf = malloc (bufsize);\n>> +\n>> +      if (buf == NULL)\n>> +\tresult = -1;\n>> +      else\n>> +\t{\n>> +\t  DIR *st = data->dirstreams[data->actdir]->stream;\n>> +\t  struct dirent64 *d;\n>> +\t  size_t actsize = 0;\n>> +\n>> +\t  while ((d = __readdir64 (st)) != NULL)\n>> +\t    {\n>> +\t      size_t this_len = NAMLEN (d);\n>> +\t      if (actsize + this_len + 2 >= bufsize)\n>> +\t\t{\n>> +\t\t  char *newp;\n>> +\t\t  bufsize += MAX (1024, 2 * this_len);\n>> +\t\t  newp = (char *) realloc (buf, bufsize);\n>> +\t\t  if (newp == NULL)\n>> +\t\t    {\n>> +\t\t      /* No more memory.  */\n>> +\t\t      free (buf);\n>> +\t\t      return -1;\n>> +\t\t    }\n>> +\t\t  buf = newp;\n>> +\t\t}\n>> +\n>> +\t      *((char *) __mempcpy (buf + actsize, d->d_name, this_len))\n>> +\t\t= '\\0';\n>> +\t      actsize += this_len + 1;\n>> +\t    }\n>> +\n>> +\t  /* Terminate the list with an additional NUL byte.  */\n>> +\t  buf[actsize++] = '\\0';\n>> +\n>> +\t  /* Shrink the buffer to what we actually need.  */\n>> +\t  void *content = realloc (buf, actsize);\n>> +\t  data->dirstreams[data->actdir]->content = content;\n>> +\t  if (content == NULL)\n>> +\t    {\n>> +\t      free (buf);\n>> +\t      result = -1;\n>> +\t    }\n>> +\t  else\n>> +\t    {\n>> +\t      __closedir (st);\n>> +\t      data->dirstreams[data->actdir]->stream = NULL;\n>> +\t      data->dirstreams[data->actdir]->streamfd = -1;\n>> +\t      data->dirstreams[data->actdir] = NULL;\n>> +\t    }\n>> +\t}\n>> +    }\n>> +\n>> +  /* Open the new stream.  */\n>> +  if (result == 0)\n>> +    {\n>> +      assert (data->dirstreams[data->actdir] == NULL);\n>> +\n>> +      if (dfdp != NULL && *dfdp != -1)\n>> +\t{\n>> +\t  int fd = __openat64_nocancel (*dfdp, data->dirbuf + data->ftw.base,\n>> +\t\t\t\t\tO_RDONLY | O_DIRECTORY | O_NDELAY);\n>> +\t  dirp->stream = NULL;\n>> +\t  if (fd != -1 && (dirp->stream = __fdopendir (fd)) == NULL)\n>> +\t    __close_nocancel_nostatus (fd);\n>> +\t}\n>> +      else\n>> +\t{\n>> +\t  const char *name;\n>> +\n>> +\t  if (data->flags & FTW_CHDIR)\n>> +\t    {\n>> +\t      name = data->dirbuf + data->ftw.base;\n>> +\t      if (name[0] == '\\0')\n>> +\t\tname = \".\";\n>> +\t    }\n>> +\t  else\n>> +\t    name = data->dirbuf;\n>> +\n>> +\t  dirp->stream = __opendir (name);\n>> +\t}\n>> +\n>> +      if (dirp->stream == NULL)\n>> +\tresult = -1;\n>> +      else\n>> +\t{\n>> +\t  dirp->streamfd = __dirfd (dirp->stream);\n>> +\t  dirp->content = NULL;\n>> +\t  data->dirstreams[data->actdir] = dirp;\n>> +\n>> +\t  if (++data->actdir == data->maxdir)\n>> +\t    data->actdir = 0;\n>> +\t}\n>> +    }\n>> +\n>> +  return result;\n>> +}\n>> +\n>> +\n>> +static int\n>> +process_entry (struct ftw_data *data, struct dir_data *dir, const char *name,\n>> +\t       size_t namlen, struct STRUCT_STAT *out_st, bool *descend)\n>> +{\n>> +  struct STRUCT_STAT st;\n>> +  int result = 0;\n>> +  int flag = 0;\n>> +  size_t new_buflen;\n>> +\n>> +  *descend = false;\n>> +\n>> +  if (name[0] == '.' && (name[1] == '\\0'\n>> +\t\t\t || (name[1] == '.' && name[2] == '\\0')))\n>> +    /* Don't process the \".\" and \"..\" entries.  */\n>> +    return 0;\n>> +\n>> +  new_buflen = data->ftw.base + namlen + 2;\n>> +  if (data->dirbufsize < new_buflen\n>> +      && !ftw_allocate (data, 2 * new_buflen))\n>> +    return -1;\n>> +\n>> +  *((char *) __mempcpy (data->dirbuf + data->ftw.base, name, namlen)) = '\\0';\n>> +\n>> +  int statres;\n>> +  if (dir->streamfd != -1)\n>> +    statres = FSTATAT (dir->streamfd, name, &st,\n>> +\t\t       (data->flags & FTW_PHYS) ? AT_SYMLINK_NOFOLLOW : 0);\n>> +  else\n>> +    {\n>> +      if ((data->flags & FTW_CHDIR) == 0)\n>> +\tname = data->dirbuf;\n>> +\n>> +      statres = ((data->flags & FTW_PHYS)\n>> +\t\t ? LSTAT (name, &st)\n>> +\t\t : STAT (name, &st));\n>> +    }\n>> +\n>> +  if (statres < 0)\n>> +    {\n>> +      if (errno != EACCES && errno != ENOENT)\n>> +\tresult = -1;\n>> +      else if (data->flags & FTW_PHYS)\n>> +\tflag = FTW_NS;\n>> +      else\n>> +\t{\n>> +\t  /* Old code left ST undefined for dangling DT_LNK without\n>> +\t     FTW_PHYS set; a clarification at the POSIX level suggests\n>> +\t     it should contain information about the link (ala lstat).\n>> +\t     We do our best to fill in what data we can.  */\n>> +\t  if (dir->streamfd != -1)\n>> +\t    statres = FSTATAT (dir->streamfd, name, &st,\n>> +\t\t\t       AT_SYMLINK_NOFOLLOW);\n>> +\t  else\n>> +\t    statres = LSTAT (name, &st);\n>> +\t  if (statres == 0 && S_ISLNK (st.st_mode))\n>> +\t    flag = FTW_SLN;\n>> +\t  else\n>> +\t    flag = FTW_NS;\n>> +\t}\n>> +    }\n>> +  else\n>> +    {\n>> +      if (S_ISDIR (st.st_mode))\n>> +\tflag = FTW_D;\n>> +      else if (S_ISLNK (st.st_mode))\n>> +\tflag = FTW_SL;\n>> +      else\n>> +\tflag = FTW_F;\n>> +    }\n>> +\n>> +  if (result == 0\n>> +      && (flag == FTW_NS\n>> +\t  || !(data->flags & FTW_MOUNT) || st.st_dev == data->dev))\n>> +    {\n>> +      if (flag == FTW_D)\n>> +\t{\n>> +\t  if ((data->flags & FTW_PHYS)\n>> +\t      || (!find_object (data, &st)\n>> +\t\t  /* Remember the object.  */\n>> +\t\t  && (result = add_object (data, &st)) == 0))\n>> +\t    {\n>> +               *out_st = st;\n>> +               *descend = true;\n>> +\t    }\n>> +\t}\n>> +      else\n>> +\tresult = CALL_FUNC (data, data->dirbuf, &st, data->cvt_arr[flag],\n>> +\t\t\t    &data->ftw);\n>> +    }\n>> +\n>> +  if ((data->flags & FTW_ACTIONRETVAL) && result == FTW_SKIP_SUBTREE)\n>> +    result = 0;\n>> +\n>> +  return result;\n>> +}\n>> +\n>> +\n>> +/* The ftw_frame are kept as chunked array to minimize the reallocation cost\n>> +   when the stack grows (since it contains STRUCT_STAT and extra metadata).\n>> +   New chunks of ftw_framw are allocated and only freed when ftw returns.  */\n>> +enum\n>> +{\n>> +  FTW_STACK_CHUNK_BLOCKS  = 1,  /* Number of initial allocated chunks.  */\n>> +  FTW_STACK_CHUNK_SIZE    = 32  /* Number of stack frames allocated per\n>> +\t\t\t\t   chunk.  */\n>> +};\n>> +\n>> +static inline struct ftw_frame *\n>> +frame_stack_get (struct ftw_stack *ftwst, int adj)\n>> +{\n>> +  return &ftwst->stack[(ftwst->top + adj) / FTW_STACK_CHUNK_SIZE]\n>> +    [(ftwst->top + adj) % FTW_STACK_CHUNK_SIZE];\n>> +}\n>> +\n>> +static inline void\n>> +frame_stack_reset_top (struct ftw_stack *fwtst, const struct STRUCT_STAT *st)\n>> +{\n>> +  struct ftw_frame *frame = frame_stack_get (fwtst, 0);\n>> +  frame->st = *st;\n>> +  frame->state = FTW_STATE_INIT;\n>> +  frame->dir.stream = NULL;\n>> +  frame->dir.content = NULL;\n>> +  frame->dir.streamfd = -1;\n>> +}\n>> +\n>> +static bool\n>> +frame_stack_init (struct ftw_stack *ftwst, const struct STRUCT_STAT *st)\n>> +{\n>> +  ftwst->num_blocks = FTW_STACK_CHUNK_BLOCKS;\n>> +  ftwst->stack = malloc (FTW_STACK_CHUNK_BLOCKS * sizeof (*ftwst->stack));\n>> +  if (ftwst->stack == NULL)\n>> +    return false;\n>> +\n>> +  ftwst->stack[0] = malloc (FTW_STACK_CHUNK_SIZE * sizeof (struct ftw_frame));\n>> +  if (ftwst->stack[0] == NULL)\n>> +    {\n>> +      free (ftwst->stack);\n>> +      return false;\n>> +    }\n>> +\n>> +  ftwst->top = 0;\n>> +  frame_stack_reset_top (ftwst, st);\n>> +  return true;\n>> +}\n>> +\n>> +static void\n>> +frame_stack_free (struct ftw_stack *ftwst)\n>> +{\n>> +  for (size_t i = 0; i < ftwst->num_blocks; i++)\n>> +    free (ftwst->stack[i]);\n>> +  free (ftwst->stack);\n>> +}\n>> +\n>> +static bool\n>> +frame_stack_add (struct ftw_stack *ftwst, const struct STRUCT_STAT *st)\n>> +{\n>> +  if (ftwst->top + 1 >= ftwst->num_blocks * FTW_STACK_CHUNK_SIZE)\n>> +    {\n>> +      size_t new_blocks = ftwst->num_blocks + 1;\n>> +      struct ftw_frame **new_stack = realloc (\n>> +\t  ftwst->stack, new_blocks * sizeof (*ftwst->stack));\n>> +\n>> +      if (new_stack == NULL)\n>> +\treturn false;\n>> +      ftwst->stack = new_stack;\n>> +      ftwst->stack[ftwst->num_blocks] = malloc (\n>> +\t  FTW_STACK_CHUNK_SIZE * sizeof (struct ftw_frame));\n>> +      if (ftwst->stack[ftwst->num_blocks] == NULL)\n>> +\treturn false;\n>> +      ftwst->num_blocks = new_blocks;\n>> +    }\n>> +  ftwst->top++;\n>> +  frame_stack_reset_top (ftwst, st);\n>> +  return true;\n>> +}\n>> +\n>> +static void\n>> +frame_closedir (struct ftw_data *data, struct ftw_frame *frame)\n>> +{\n>> +  int save_err = errno;\n>> +  assert (frame->dir.content == NULL);\n>> +  __closedir (frame->dir.stream);\n>> +  frame->dir.streamfd = -1;\n>> +  __set_errno (save_err);\n>> +  if (data->actdir-- == 0)\n>> +    data->actdir = data->maxdir - 1;\n>> +  data->dirstreams[data->actdir] = NULL;\n>> +  frame->dir.stream = NULL;\n>> +}\n>> +\n>> +static int\n>> +ftw_dir (struct ftw_data *data, const struct STRUCT_STAT *st)\n>> +{\n>> +  struct ftw_stack ftwst;\n>> +  if (!frame_stack_init (&ftwst, st))\n>> +    return -1;\n>> +\n>> +  int result = 0;\n>> +\n>> +  while (ftwst.top >= 0)\n>> +    {\n>> +      struct ftw_frame *frame = frame_stack_get (&ftwst, 0);\n>> +      struct dir_data *old_dir = (ftwst.top > 0)\n>> +\t? &frame_stack_get (&ftwst, -1)->dir : NULL;\n>> +\n>> +      if (frame->state == FTW_STATE_INIT)\n>> +\t{\n>> +\t  frame->previous_base = data->ftw.base;\n>> +\t  result = open_dir_stream (\n>> +\t      old_dir == NULL ? NULL : &old_dir->streamfd, data, &frame->dir);\n>> +\t  if (result != 0)\n>> +\t    {\n>> +\t      if (errno == EACCES)\n>> +\t\tresult = CALL_FUNC (data, data->dirbuf, &frame->st, FTW_DNR,\n>> +\t\t\t\t    &data->ftw);\n>> +\t      ftwst.top--;\n>> +\t      /* Intercept FTW_SKIP_SUBTREE when popping frame */\n>> +\t      if (ftwst.top >= 0 && (data->flags & FTW_ACTIONRETVAL)\n>> +\t\t  && result == FTW_SKIP_SUBTREE)\n>> +\t\tresult = 0;\n>> +\t      continue;\n>> +\t    }\n>> +\n>> +\t  if (!(data->flags & FTW_DEPTH))\n>> +\t    {\n>> +\t      result = CALL_FUNC (data, data->dirbuf, &frame->st, FTW_D,\n>> +\t\t\t\t  &data->ftw);\n>> +\t      if (result != 0)\n>> +\t\tgoto state0_fail;\n>> +\t    }\n>> +\n>> +\t  if (data->flags & FTW_CHDIR)\n>> +\t    {\n>> +\t      if (__fchdir (__dirfd (frame->dir.stream)) < 0)\n>> +\t\t{\n>> +\t\t  result = -1;\n>> +\t\tstate0_fail:\n>> +\t\t  frame_closedir (data, frame);\n>> +\t\t  ftwst.top--;\n>> +\t\t  /* Intercept FTW_SKIP_SUBTREE when popping frame.  */\n>> +\t\t  if (ftwst.top >= 0 && (data->flags & FTW_ACTIONRETVAL)\n>> +\t\t      && result == FTW_SKIP_SUBTREE)\n>> +\t\t    result = 0;\n>> +\t\t  continue;\n>> +\t\t}\n>> +\t    }\n>> +\n>> +\t  ++data->ftw.level;\n>> +\t  char *startp = strchr (data->dirbuf, '\\0');\n>> +\t  assert (startp != data->dirbuf);\n>> +\t  if (startp[-1] != '/')\n>> +\t    *startp++ = '/';\n>> +\t  data->ftw.base = startp - data->dirbuf;\n>> +\n>> +\t  frame->state = FTW_STATE_STREAM_LOOP;\n>> +\t  frame->runp = frame->dir.content;\n>> +\t}\n>> +      else if (frame->state == FTW_STATE_STREAM_LOOP)\n>> +\t{\n>> +\t  if (result != 0)\n>> +\t    {\n>> +\t      frame->state = FTW_STATE_CLEANUP;\n>> +\t      continue;\n>> +\t    }\n>> +\n>> +\t  if (frame->dir.stream == NULL)\n>> +\t    {\n>> +\t      frame->state = FTW_STATE_CONTENT_LOOP;\n>> +\t      frame->runp = frame->dir.content;\n>> +\t      continue;\n>> +\t    }\n>> +\n>> +\t  struct dirent64 *d = __readdir64 (frame->dir.stream);\n>> +\t  if (d != NULL)\n>> +\t    {\n>> +\t      struct STRUCT_STAT child_st;\n>> +\t      bool descend = false;\n>> +\t      result = process_entry (data, &frame->dir, d->d_name, NAMLEN (d),\n>> +\t\t\t\t      &child_st, &descend);\n>> +\n>> +\t      if (result == 0 && descend)\n>> +\t\t{\n>> +\t\t  if (!frame_stack_add (&ftwst, &child_st))\n>> +\t\t    {\n>> +\t\t      result = -1;\n>> +\t\t      frame->state = FTW_STATE_CLEANUP;\n>> +\t\t    }\n>> +\t\t  continue;\n>> +\t\t}\n>> +\t      else if (result != 0)\n>> +\t\t{\n>> +\t\t  frame->state = FTW_STATE_CLEANUP;\n>> +\t\t  continue;\n>> +\t\t}\n>> +\t    }\n>> +\t  else\n>> +\t    frame->state = FTW_STATE_CLEANUP;\n>> +\t}\n>> +      else if (frame->state == FTW_STATE_CONTENT_LOOP)\n>> +\t{\n>> +\t  /* Check if we are safely positioned to process the starting path.\n>> +\t     The 'result' variable here comes from one of two places:\n>> +\n>> +\t     1. Initialization: defaults to 0 at the top of ftw_startup.  If\n>> +\t        the FTW_CHDIR flag was NOT passed, it remains 0, meaning we\n>> +\t\tare good to go.\n>> +\n>> +\t     2. Directory Change: If FTW_CHDIR WAS passed, 'result' holds the\n>> +\t        return value of the preceding __chdir() call (either moving\n>> +\t\tto \"/\" or the parsed base directory).\n>> +\n>> +\t     If 'result' is 0, the setup succeeded (or wasn't needed) and we\n>> +\t     can safely stat the initial object.  Othewise, the chdir failed,\n>> +\t     so we skip processing and fall through to the cleanup phase.  */\n>> +\t  if (result != 0)\n>> +\t    {\n>> +\t      frame->state = FTW_STATE_CLEANUP;\n>> +\t      continue;\n>> +\t    }\n>> +\n>> +\t  if (frame->runp != NULL && *frame->runp != '\\0')\n>> +\t    {\n>> +\t      char *endp = strchr (frame->runp, '\\0');\n>> +\t      struct STRUCT_STAT child_st;\n>> +\t      bool descend = false;\n>> +\n>> +\t      result = process_entry (data, &frame->dir, frame->runp,\n>> +\t\t\t\t      endp - frame->runp, &child_st,\n>> +\t\t\t\t      &descend);\n>> +\t      frame->runp = endp + 1;\n>> +\n>> +\t      if (result == 0 && descend)\n>> +\t\t{\n>> +\t\t  if (!frame_stack_add (&ftwst, &child_st))\n>> +\t\t    {\n>> +\t\t      result = -1;\n>> +\t\t      frame->state = FTW_STATE_CLEANUP;\n>> +\t\t    }\n>> +\t\t  continue;\n>> +\t\t}\n>> +\t      else if (result != 0)\n>> +\t\t{\n>> +\t\t  frame->state = FTW_STATE_CLEANUP;\n>> +\t\t  continue;\n>> +\t\t}\n>> +\t    }\n>> +\t  else\n>> +\t    frame->state = FTW_STATE_CLEANUP;\n>> +\t}\n>> +      else if (frame->state == FTW_STATE_CLEANUP)\n>> +\t{\n>> +\t  if (frame->dir.stream != NULL)\n>> +\t    frame_closedir (data, frame);\n>> +\t  else if (frame->dir.content != NULL)\n>> +\t    {\n>> +\t      free (frame->dir.content);\n>> +\t      frame->dir.content = NULL;\n>> +\t    }\n>> +\n>> +\t  if ((data->flags & FTW_ACTIONRETVAL) && result == FTW_SKIP_SIBLINGS)\n>> +\t    result = 0;\n>> +\n>> +\t  data->dirbuf[data->ftw.base - 1] = '\\0';\n>> +\t  --data->ftw.level;\n>> +\t  data->ftw.base = frame->previous_base;\n>> +\n>> +\t  if (result == 0 && (data->flags & FTW_DEPTH))\n>> +\t    result\n>> +\t\t= CALL_FUNC (data, data->dirbuf, &frame->st, FTW_DP,\n>> +\t\t\t     &data->ftw);\n>> +\n>> +\t  if (old_dir != NULL && (data->flags & FTW_CHDIR)\n>> +\t      && (result == 0\n>> +\t\t  || ((data->flags & FTW_ACTIONRETVAL)\n>> +\t\t      && (result != -1 && result != FTW_STOP))))\n>> +\t    {\n>> +\t      int done = 0;\n>> +\t      if (old_dir->stream != NULL)\n>> +\t\tif (__fchdir (__dirfd (old_dir->stream)) == 0)\n>> +\t\t  done = 1;\n>> +\n>> +\t      if (!done)\n>> +\t\t{\n>> +\t\t  if (data->ftw.base == 1)\n>> +\t\t    {\n>> +\t\t      if (__chdir (\"/\") < 0)\n>> +\t\t\tresult = -1;\n>> +\t\t    }\n>> +\t\t  else if (__chdir (\"..\") < 0)\n>> +\t\t    result = -1;\n>> +\t\t}\n>> +\t    }\n>> +\n>> +\t  ftwst.top--;\n>> +\t  /* Intercept FTW_SKIP_SUBTREE when popping frame.  */\n>> +\t  if (ftwst.top >= 0 && (data->flags & FTW_ACTIONRETVAL)\n>> +\t      && result == FTW_SKIP_SUBTREE)\n>> +\t    result = 0;\n>> +\t}\n>> +    }\n>> +\n>> +  frame_stack_free (&ftwst);\n>> +\n>> +  return result;\n>> +}\n>> +\n>> +\n>> +static int\n>> +ftw_startup (const char *dir, bool is_nftw, func_callback_t func,\n>> +\t     int descriptors, int flags)\n>> +{\n>> +  struct ftw_data data = { .dirstreams = NULL };\n>> +  struct STRUCT_STAT st;\n>> +  int result = 0;\n>> +  int cwdfd = -1;\n>> +  char *cwd = NULL;\n>> +  char *cp;\n>> +\n>> +  /* First make sure the parameters are reasonable.  */\n>> +  if (dir[0] == '\\0')\n>> +    {\n>> +      __set_errno (ENOENT);\n>> +      return -1;\n>> +    }\n>> +\n>> +  data.maxdir = descriptors < 1 ? 1 : descriptors;\n>> +  data.actdir = 0;\n>> +  /* PATH_MAX is always defined when we get here.  */\n>> +  if (!ftw_allocate (&data, MAX (2 * strlen (dir), PATH_MAX)))\n>> +    return -1;\n>> +  memset (data.dirstreams, '\\0', data.maxdir * sizeof (struct dir_data *));\n>> +  cp = __stpcpy (data.dirbuf, dir);\n>> +  /* Strip trailing slashes.  */\n>> +  while (cp > data.dirbuf + 1 && cp[-1] == '/')\n>> +    --cp;\n>> +  *cp = '\\0';\n>> +\n>> +  data.ftw.level = 0;\n>> +\n>> +  /* Find basename.  */\n>> +  while (cp > data.dirbuf && cp[-1] != '/')\n>> +    --cp;\n>> +  data.ftw.base = cp - data.dirbuf;\n>> +\n>> +  data.flags = flags;\n>> +\n>> +  data.is_nftw = is_nftw;\n>> +  data.func = func;\n>> +\n>> +  /* Since we internally use the complete set of FTW_* values we need\n>> +     to reduce the value range before calling a `ftw' callback.  */\n>> +  data.cvt_arr = is_nftw ? nftw_arr : ftw_arr;\n>> +\n>> +  /* No object known so far.  */\n>> +  data.known_objects = NULL;\n>> +\n>> +  /* Now go to the directory containing the initial file/directory.  */\n>> +  if (flags & FTW_CHDIR)\n>> +    {\n>> +      /* We have to be able to go back to the current working\n>> +\t directory.  The best way to do this is to use a file\n>> +\t descriptor.  */\n>> +      cwdfd = __open (\".\", O_RDONLY | O_DIRECTORY);\n>> +      if (cwdfd == -1)\n>> +\t{\n>> +\t  /* Try getting the directory name.  This can be needed if\n>> +\t     the current directory is executable but not readable.  */\n>> +\t  if (errno == EACCES)\n>> +\t    /* GNU extension ahead.  */\n>> +\t    cwd =  __getcwd (NULL, 0);\n>> +\n>> +\t  if (cwd == NULL)\n>> +\t    goto out_fail;\n>> +\t}\n>> +      else if (data.maxdir > 1)\n>> +\t/* Account for the file descriptor we use here.  */\n>> +\t--data.maxdir;\n>> +\n>> +      if (data.ftw.base > 0)\n>> +\t{\n>> +\t  /* Change to the directory the file is in.  In data.dirbuf\n>> +\t     we have a writable copy of the file name.  Just NUL\n>> +\t     terminate it for now and change the directory.  */\n>> +\t  if (data.ftw.base == 1)\n>> +\t    /* I.e., the file is in the root directory.  */\n>> +\t    result = __chdir (\"/\");\n>> +\t  else\n>> +\t    {\n>> +\t      char ch = data.dirbuf[data.ftw.base - 1];\n>> +\t      data.dirbuf[data.ftw.base - 1] = '\\0';\n>> +\t      result = __chdir (data.dirbuf);\n>> +\t      data.dirbuf[data.ftw.base - 1] = ch;\n>> +\t    }\n>> +\t}\n>> +    }\n>> +\n>> +  /* Get stat info for start directory.  */\n>> +  if (result == 0)\n>> +    {\n>> +      const char *name;\n>> +\n>> +      if (data.flags & FTW_CHDIR)\n>> +\t{\n>> +\t  name = data.dirbuf + data.ftw.base;\n>> +\t  if (name[0] == '\\0')\n>> +\t    name = \".\";\n>> +\t}\n>> +      else\n>> +\tname = data.dirbuf;\n>> +\n>> +      if (((flags & FTW_PHYS)\n>> +\t   ? LSTAT (name, &st)\n>> +\t   : STAT (name, &st)) < 0)\n>> +\t{\n>> +\t  if (!(flags & FTW_PHYS)\n>> +\t      && errno == ENOENT\n>> +\t      && LSTAT (name, &st) == 0\n>> +\t      && S_ISLNK (st.st_mode))\n>> +\t    result = CALL_FUNC (&data, data.dirbuf, &st, data.cvt_arr[FTW_SLN],\n>> +\t\t\t\t&data.ftw);\n>> +\t  else\n>> +\t    /* No need to call the callback since we cannot say anything\n>> +\t       about the object.  */\n>> +\t    result = -1;\n>> +\t}\n>> +      else\n>> +\t{\n>> +\t  if (S_ISDIR (st.st_mode))\n>> +\t    {\n>> +\t      /* Remember the device of the initial directory in case\n>> +\t\t FTW_MOUNT is given.  */\n>> +\t      data.dev = st.st_dev;\n>> +\n>> +\t      /* We know this directory now.  */\n>> +\t      if (!(flags & FTW_PHYS))\n>> +\t\tresult = add_object (&data, &st);\n>> +\n>> +\t      if (result == 0)\n>> +\t\tresult = ftw_dir (&data, &st);\n>> +\t    }\n>> +\t  else\n>> +\t    {\n>> +\t      int flag = S_ISLNK (st.st_mode) ? FTW_SL : FTW_F;\n>> +\n>> +\t      result = CALL_FUNC (&data, data.dirbuf, &st, data.cvt_arr[flag],\n>> +\t\t\t\t  &data.ftw);\n>> +\t    }\n>> +\t}\n>> +\n>> +      if ((flags & FTW_ACTIONRETVAL)\n>> +\t  && (result == FTW_SKIP_SUBTREE || result == FTW_SKIP_SIBLINGS))\n>> +\tresult = 0;\n>> +    }\n>> +\n>> +  /* Return to the start directory (if necessary).  */\n>> +  if (cwdfd != -1)\n>> +    {\n>> +      int save_err = errno;\n>> +      __fchdir (cwdfd);\n>> +      __close_nocancel_nostatus (cwdfd);\n>> +      __set_errno (save_err);\n>> +    }\n>> +  else if (cwd != NULL)\n>> +    {\n>> +      int save_err = errno;\n>> +      __chdir (cwd);\n>> +      free (cwd);\n>> +      __set_errno (save_err);\n>> +    }\n>> +\n>> +  /* Free all memory.  */\n>> + out_fail:\n>> +  __tdestroy (data.known_objects, free);\n>> +  free (data.dirstreams);\n>> +\n>> +  return result;\n>> +}\n>> +\n>> +\n>> +\n>> +/* Entry points.  */\n>> +\n>> +int\n>> +FTW_NAME (const char *path, FTW_FUNC_T func, int descriptors)\n>> +{\n>> +  return ftw_startup (path, false, (func_callback_t) { .ftw_func = func },\n>> +\t\t      descriptors, 0);\n>> +}\n>> +\n>> +#ifndef NFTW_OLD_NAME\n>> +int\n>> +NFTW_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags)\n>> +{\n>> +  return ftw_startup (path, true, (func_callback_t) { .nftw_func = func },\n>> +\t\t      descriptors, flags);\n>> +}\n>> +#else\n>> +\n>> +# include <shlib-compat.h>\n>> +\n>> +int NFTW_NEW_NAME (const char *, NFTW_FUNC_T, int, int);\n>> +\n>> +int\n>> +NFTW_NEW_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags)\n>> +{\n>> +  if (flags\n>> +      & ~(FTW_PHYS | FTW_MOUNT | FTW_CHDIR | FTW_DEPTH | FTW_ACTIONRETVAL))\n>> +    {\n>> +      __set_errno (EINVAL);\n>> +      return -1;\n>> +    }\n>> +  return ftw_startup (path, true, (func_callback_t) { .nftw_func = func },\n>> +\t\t      descriptors, flags);\n>> +}\n>> +\n>> +# if SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_3_3)\n>> +\n>> +/* Older nftw* version just ignored all unknown flags.  */\n>> +\n>> +int NFTW_OLD_NAME (const char *, NFTW_FUNC_T, int, int);\n>> +\n>> +int\n>> +attribute_compat_text_section\n>> +NFTW_OLD_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags)\n>> +{\n>> +  flags &= (FTW_PHYS | FTW_MOUNT | FTW_CHDIR | FTW_DEPTH);\n>> +  return ftw_startup (path, true, (func_callback_t) { .nftw_func = func },\n>> +\t\t      descriptors, flags);\n>> +}\n>> +\n>> +# endif\n>> +#endif /* NFTW_OLD_NAME  */\n>> diff --git a/io/ftw.c b/io/ftw.c\n>> index 726c430eaf..ed0eeb3904 100644\n>> --- a/io/ftw.c\n>> +++ b/io/ftw.c\n>> @@ -1,5 +1,5 @@\n>> -/* File tree walker functions.\n>> -   Copyright (C) 1996-2026 Free Software Foundation, Inc.\n>> +/* File tree traversal functions LFS version.\n>> +   Copyright (C) 2026 Free Software Foundation, Inc.\n>>      This file is part of the GNU C Library.\n>>   \n>>      The GNU C Library is free software; you can redistribute it and/or\n>> @@ -16,956 +16,14 @@\n>>      License along with the GNU C Library; if not, see\n>>      <https://www.gnu.org/licenses/>.  */\n>>   \n>> +#include <sys/types.h>\n>>   \n>> -#include <assert.h>\n>> -#include <dirent.h>\n>> -#include <fcntl.h>\n>> -#include <ftw.h>\n>> -#include <not-cancel.h>\n>> -#include <search.h>\n>> -#include <unistd.h>\n>> -#include <sys/param.h>\n>> +#ifndef __OFF_T_MATCHES_OFF64_T\n>> +# include \"ftw-common.c\"\n>>   \n>> -#define NAMLEN(dirent) _D_EXACT_NAMLEN (dirent)\n>> -\n>> -/* Support for the LFS API version.  */\n>> -#ifndef FTW_NAME\n>> -# define FTW_NAME ftw\n>> -# define NFTW_NAME nftw\n>> -# define NFTW_OLD_NAME __old_nftw\n>> -# define NFTW_NEW_NAME __new_nftw\n>> -# define INO_T ino_t\n>> -# define STRUCT_STAT stat\n>> -# define LSTAT __lstat\n>> -# define STAT __stat\n>> -# define FSTATAT __fstatat\n>> -# define FTW_FUNC_T __ftw_func_t\n>> -# define NFTW_FUNC_T __nftw_func_t\n>> +versioned_symbol (libc, __new_nftw, nftw, GLIBC_2_3_3);\n>> +#if SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_3_3)\n>> +compat_symbol (libc, __old_nftw, nftw, GLIBC_2_1);\n>>   #endif\n>>   \n>> -/* We define PATH_MAX if the system does not provide a definition.\n>> -   This does not artificially limit any operation.  PATH_MAX is simply\n>> -   used as a guesstimate for the expected maximal path length.\n>> -   Buffers will be enlarged if necessary.  */\n>> -#ifndef PATH_MAX\n>> -# define PATH_MAX 1024\n>>   #endif\n>> -\n>> -struct dir_data\n>> -{\n>> -  DIR *stream;\n>> -  int streamfd;\n>> -  char *content;\n>> -};\n>> -\n>> -struct known_object\n>> -{\n>> -  dev_t dev;\n>> -  INO_T ino;\n>> -};\n>> -\n>> -/* Represents the execution state of a directory processing frame within the\n>> -   iterative file tree walk loop.\n>> -\n>> -   Because the tree traversal is implemented iteratively using a custom stack\n>> -   rather than standard recursion, this state machine tracks the progress\n>> -   of each directory currently being visited.  */\n>> -enum ftw_frame_state\n>> -{\n>> -  /* The initial state of a newly pushed directory frame.  Attempts to open\n>> -     the directory stream.  If successful, transitions to\n>> -     FTW_STATE_STREAM_LOOP.  */\n>> -  FTW_STATE_INIT = 0,\n>> -\n>> -  /* Iterating over the directory entries directly from the open DIR stream\n>> -     (using readdir).  If a subdirectory is encountered and needs to be\n>> -     descended into, a new frame is added to the stack and execution pauses\n>> -     here.  Transitions to FTW_STATE_CONTENT_LOOP if the stream was closed\n>> -     and cached to free up file descriptors, or FTW_STATE_CLEANUP when\n>> -     done.  */\n>> -  FTW_STATE_STREAM_LOOP,\n>> -\n>> -  /* Iterating over directory entries from a cached memory buffer.  This state\n>> -     is used as a fallback when the original DIR stream had to be closed\n>> -     prematurely to prevent file descriptor exhaustion while descending into\n>> -     deeply nested child directories.  Transitions to FTW_STATE_CLEANUP when\n>> -     all cached entries are processed.  */\n>> -  FTW_STATE_CONTENT_LOOP,\n>> -\n>> -  /* The final state, handles resource deallocation (closing remaining\n>> -     streams, freeing cached content buffers), triggering post-traversal\n>> -     callbacks (like FTW_DP for FTW_DEPTH walks), and restoring the\n>> -     previous working directory if FTW_CHDIR was used.  */\n>> -  FTW_STATE_CLEANUP\n>> -};\n>> -\n>> -/* Keep track of visited directories.  */\n>> -struct ftw_frame\n>> -{\n>> -  struct dir_data dir;\n>> -  struct STRUCT_STAT st;\n>> -  int previous_base;\n>> -  char *runp;\n>> -  enum ftw_frame_state state;\n>> -};\n>> -\n>> -struct ftw_stack\n>> -{\n>> -  struct ftw_frame **stack;\n>> -  size_t num_blocks;\n>> -  ssize_t top;\n>> -};\n>> -\n>> -typedef union\n>> -{\n>> -  NFTW_FUNC_T nftw_func;\n>> -  FTW_FUNC_T ftw_func;\n>> -} func_callback_t;\n>> -\n>> -struct ftw_data\n>> -{\n>> -  /* Array with pointers to open directory streams.  */\n>> -  struct dir_data **dirstreams;\n>> -  size_t actdir;\n>> -  size_t maxdir;\n>> -\n>> -  /* Buffer containing name of currently processed object.  */\n>> -  char *dirbuf;\n>> -  size_t dirbufsize;\n>> -\n>> -  /* Passed as fourth argument to `nftw' callback.  The `base' member\n>> -     tracks the content of the `dirbuf'.  */\n>> -  struct FTW ftw;\n>> -\n>> -  /* Flags passed to `nftw' function.  0 for `ftw'.  */\n>> -  int flags;\n>> -\n>> -  /* Conversion array for flag values.  It is the identity mapping for\n>> -     `nftw' calls, otherwise it maps the values to those known by\n>> -     `ftw'.  */\n>> -  const int *cvt_arr;\n>> -\n>> -  /* Callback function.  We always use the `nftw' form.  */\n>> -  bool is_nftw;\n>> -  func_callback_t func;\n>> -\n>> -  /* Device of starting point.  Needed for FTW_MOUNT.  */\n>> -  dev_t dev;\n>> -\n>> -  /* Data structure for keeping fingerprints of already processed\n>> -     object.  This is needed when not using FTW_PHYS.  */\n>> -  void *known_objects;\n>> -};\n>> -#define CALL_FUNC(__ftw_data, __fp, __sb, __f, __ftw)                            \\\n>> -  ((__ftw_data)->is_nftw ? (__ftw_data)->func.nftw_func (__fp, __sb, __f, __ftw) \\\n>> -                         : (__ftw_data)->func.ftw_func (__fp, __sb, __f))\n>> -\n>> -static bool\n>> -ftw_allocate (struct ftw_data *data, size_t newsize)\n>> -{\n>> -  void *newp = realloc (data->dirstreams, data->maxdir\n>> -\t\t\t\t\t  * sizeof (struct dir_data *)\n>> -\t\t\t\t\t  + newsize);\n>> -  if (newp == NULL)\n>> -    return false;\n>> -  data->dirstreams = newp;\n>> -  data->dirbufsize = newsize;\n>> -  data->dirbuf = (char *) data->dirstreams\n>> -\t\t + data->maxdir * sizeof (struct dir_data *);\n>> -  return true;\n>> -}\n>> -\n>> -/* Internally we use the FTW_* constants used for `nftw'.  When invoked\n>> -   as `ftw', map each flag to the subset of values used by `ftw'.  */\n>> -static const int nftw_arr[] =\n>> -{\n>> -  FTW_F, FTW_D, FTW_DNR, FTW_NS, FTW_SL, FTW_DP, FTW_SLN\n>> -};\n>> -\n>> -static const int ftw_arr[] =\n>> -{\n>> -  FTW_F, FTW_D, FTW_DNR, FTW_NS, FTW_F, FTW_D, FTW_NS\n>> -};\n>> -\n>> -\n>> -static int\n>> -object_compare (const void *p1, const void *p2)\n>> -{\n>> -  /* We don't need a sophisticated and useful comparison.  We are only\n>> -     interested in equality.  However, we must be careful not to\n>> -     accidentally compare `holes' in the structure.  */\n>> -  const struct known_object *kp1 = p1, *kp2 = p2;\n>> -  int cmp1;\n>> -  cmp1 = (kp1->ino > kp2->ino) - (kp1->ino < kp2->ino);\n>> -  if (cmp1 != 0)\n>> -    return cmp1;\n>> -  return (kp1->dev > kp2->dev) - (kp1->dev < kp2->dev);\n>> -}\n>> -\n>> -\n>> -static int\n>> -add_object (struct ftw_data *data, struct STRUCT_STAT *st)\n>> -{\n>> -  struct known_object *newp = malloc (sizeof (struct known_object));\n>> -  if (newp == NULL)\n>> -    return -1;\n>> -  newp->dev = st->st_dev;\n>> -  newp->ino = st->st_ino;\n>> -  return __tsearch (newp, &data->known_objects, object_compare) ? 0 : -1;\n>> -}\n>> -\n>> -\n>> -static inline int\n>> -find_object (struct ftw_data *data, struct STRUCT_STAT *st)\n>> -{\n>> -  struct known_object obj;\n>> -  obj.dev = st->st_dev;\n>> -  obj.ino = st->st_ino;\n>> -  return __tfind (&obj, &data->known_objects, object_compare) != NULL;\n>> -}\n>> -\n>> -\n>> -static inline int\n>> -open_dir_stream (int *dfdp, struct ftw_data *data, struct dir_data *dirp)\n>> -{\n>> -  int result = 0;\n>> -\n>> -  if (data->dirstreams[data->actdir] != NULL)\n>> -    {\n>> -      /* Oh, oh.  We must close this stream.  Get all remaining\n>> -\t entries and store them as a list in the `content' member of\n>> -\t the `struct dir_data' variable.  */\n>> -      size_t bufsize = 1024;\n>> -      char *buf = malloc (bufsize);\n>> -\n>> -      if (buf == NULL)\n>> -\tresult = -1;\n>> -      else\n>> -\t{\n>> -\t  DIR *st = data->dirstreams[data->actdir]->stream;\n>> -\t  struct dirent64 *d;\n>> -\t  size_t actsize = 0;\n>> -\n>> -\t  while ((d = __readdir64 (st)) != NULL)\n>> -\t    {\n>> -\t      size_t this_len = NAMLEN (d);\n>> -\t      if (actsize + this_len + 2 >= bufsize)\n>> -\t\t{\n>> -\t\t  char *newp;\n>> -\t\t  bufsize += MAX (1024, 2 * this_len);\n>> -\t\t  newp = (char *) realloc (buf, bufsize);\n>> -\t\t  if (newp == NULL)\n>> -\t\t    {\n>> -\t\t      /* No more memory.  */\n>> -\t\t      free (buf);\n>> -\t\t      return -1;\n>> -\t\t    }\n>> -\t\t  buf = newp;\n>> -\t\t}\n>> -\n>> -\t      *((char *) __mempcpy (buf + actsize, d->d_name, this_len))\n>> -\t\t= '\\0';\n>> -\t      actsize += this_len + 1;\n>> -\t    }\n>> -\n>> -\t  /* Terminate the list with an additional NUL byte.  */\n>> -\t  buf[actsize++] = '\\0';\n>> -\n>> -\t  /* Shrink the buffer to what we actually need.  */\n>> -\t  void *content = realloc (buf, actsize);\n>> -\t  data->dirstreams[data->actdir]->content = content;\n>> -\t  if (content == NULL)\n>> -\t    {\n>> -\t      free (buf);\n>> -\t      result = -1;\n>> -\t    }\n>> -\t  else\n>> -\t    {\n>> -\t      __closedir (st);\n>> -\t      data->dirstreams[data->actdir]->stream = NULL;\n>> -\t      data->dirstreams[data->actdir]->streamfd = -1;\n>> -\t      data->dirstreams[data->actdir] = NULL;\n>> -\t    }\n>> -\t}\n>> -    }\n>> -\n>> -  /* Open the new stream.  */\n>> -  if (result == 0)\n>> -    {\n>> -      assert (data->dirstreams[data->actdir] == NULL);\n>> -\n>> -      if (dfdp != NULL && *dfdp != -1)\n>> -\t{\n>> -\t  int fd = __openat64_nocancel (*dfdp, data->dirbuf + data->ftw.base,\n>> -\t\t\t\t\tO_RDONLY | O_DIRECTORY | O_NDELAY);\n>> -\t  dirp->stream = NULL;\n>> -\t  if (fd != -1 && (dirp->stream = __fdopendir (fd)) == NULL)\n>> -\t    __close_nocancel_nostatus (fd);\n>> -\t}\n>> -      else\n>> -\t{\n>> -\t  const char *name;\n>> -\n>> -\t  if (data->flags & FTW_CHDIR)\n>> -\t    {\n>> -\t      name = data->dirbuf + data->ftw.base;\n>> -\t      if (name[0] == '\\0')\n>> -\t\tname = \".\";\n>> -\t    }\n>> -\t  else\n>> -\t    name = data->dirbuf;\n>> -\n>> -\t  dirp->stream = __opendir (name);\n>> -\t}\n>> -\n>> -      if (dirp->stream == NULL)\n>> -\tresult = -1;\n>> -      else\n>> -\t{\n>> -\t  dirp->streamfd = __dirfd (dirp->stream);\n>> -\t  dirp->content = NULL;\n>> -\t  data->dirstreams[data->actdir] = dirp;\n>> -\n>> -\t  if (++data->actdir == data->maxdir)\n>> -\t    data->actdir = 0;\n>> -\t}\n>> -    }\n>> -\n>> -  return result;\n>> -}\n>> -\n>> -\n>> -static int\n>> -process_entry (struct ftw_data *data, struct dir_data *dir, const char *name,\n>> -\t       size_t namlen, struct STRUCT_STAT *out_st, bool *descend)\n>> -{\n>> -  struct STRUCT_STAT st;\n>> -  int result = 0;\n>> -  int flag = 0;\n>> -  size_t new_buflen;\n>> -\n>> -  *descend = false;\n>> -\n>> -  if (name[0] == '.' && (name[1] == '\\0'\n>> -\t\t\t || (name[1] == '.' && name[2] == '\\0')))\n>> -    /* Don't process the \".\" and \"..\" entries.  */\n>> -    return 0;\n>> -\n>> -  new_buflen = data->ftw.base + namlen + 2;\n>> -  if (data->dirbufsize < new_buflen\n>> -      && !ftw_allocate (data, 2 * new_buflen))\n>> -    return -1;\n>> -\n>> -  *((char *) __mempcpy (data->dirbuf + data->ftw.base, name, namlen)) = '\\0';\n>> -\n>> -  int statres;\n>> -  if (dir->streamfd != -1)\n>> -    statres = FSTATAT (dir->streamfd, name, &st,\n>> -\t\t       (data->flags & FTW_PHYS) ? AT_SYMLINK_NOFOLLOW : 0);\n>> -  else\n>> -    {\n>> -      if ((data->flags & FTW_CHDIR) == 0)\n>> -\tname = data->dirbuf;\n>> -\n>> -      statres = ((data->flags & FTW_PHYS)\n>> -\t\t ? LSTAT (name, &st)\n>> -\t\t : STAT (name, &st));\n>> -    }\n>> -\n>> -  if (statres < 0)\n>> -    {\n>> -      if (errno != EACCES && errno != ENOENT)\n>> -\tresult = -1;\n>> -      else if (data->flags & FTW_PHYS)\n>> -\tflag = FTW_NS;\n>> -      else\n>> -\t{\n>> -\t  /* Old code left ST undefined for dangling DT_LNK without\n>> -\t     FTW_PHYS set; a clarification at the POSIX level suggests\n>> -\t     it should contain information about the link (ala lstat).\n>> -\t     We do our best to fill in what data we can.  */\n>> -\t  if (dir->streamfd != -1)\n>> -\t    statres = FSTATAT (dir->streamfd, name, &st,\n>> -\t\t\t       AT_SYMLINK_NOFOLLOW);\n>> -\t  else\n>> -\t    statres = LSTAT (name, &st);\n>> -\t  if (statres == 0 && S_ISLNK (st.st_mode))\n>> -\t    flag = FTW_SLN;\n>> -\t  else\n>> -\t    flag = FTW_NS;\n>> -\t}\n>> -    }\n>> -  else\n>> -    {\n>> -      if (S_ISDIR (st.st_mode))\n>> -\tflag = FTW_D;\n>> -      else if (S_ISLNK (st.st_mode))\n>> -\tflag = FTW_SL;\n>> -      else\n>> -\tflag = FTW_F;\n>> -    }\n>> -\n>> -  if (result == 0\n>> -      && (flag == FTW_NS\n>> -\t  || !(data->flags & FTW_MOUNT) || st.st_dev == data->dev))\n>> -    {\n>> -      if (flag == FTW_D)\n>> -\t{\n>> -\t  if ((data->flags & FTW_PHYS)\n>> -\t      || (!find_object (data, &st)\n>> -\t\t  /* Remember the object.  */\n>> -\t\t  && (result = add_object (data, &st)) == 0))\n>> -\t    {\n>> -               *out_st = st;\n>> -               *descend = true;\n>> -\t    }\n>> -\t}\n>> -      else\n>> -\tresult = CALL_FUNC (data, data->dirbuf, &st, data->cvt_arr[flag],\n>> -\t\t\t    &data->ftw);\n>> -    }\n>> -\n>> -  if ((data->flags & FTW_ACTIONRETVAL) && result == FTW_SKIP_SUBTREE)\n>> -    result = 0;\n>> -\n>> -  return result;\n>> -}\n>> -\n>> -\n>> -/* The ftw_frame are kept as chunked array to minimize the reallocation cost\n>> -   when the stack grows (since it contains STRUCT_STAT and extra metadata).\n>> -   New chunks of ftw_framw are allocated and only freed when ftw returns.  */\n>> -enum\n>> -{\n>> -  FTW_STACK_CHUNK_BLOCKS  = 1,  /* Number of initial allocated chunks.  */\n>> -  FTW_STACK_CHUNK_SIZE    = 32  /* Number of stack frames allocated per\n>> -\t\t\t\t   chunk.  */\n>> -};\n>> -\n>> -static inline struct ftw_frame *\n>> -frame_stack_get (struct ftw_stack *ftwst, int adj)\n>> -{\n>> -  return &ftwst->stack[(ftwst->top + adj) / FTW_STACK_CHUNK_SIZE]\n>> -    [(ftwst->top + adj) % FTW_STACK_CHUNK_SIZE];\n>> -}\n>> -\n>> -static inline void\n>> -frame_stack_reset_top (struct ftw_stack *fwtst, const struct STRUCT_STAT *st)\n>> -{\n>> -  struct ftw_frame *frame = frame_stack_get (fwtst, 0);\n>> -  frame->st = *st;\n>> -  frame->state = FTW_STATE_INIT;\n>> -  frame->dir.stream = NULL;\n>> -  frame->dir.content = NULL;\n>> -  frame->dir.streamfd = -1;\n>> -}\n>> -\n>> -static bool\n>> -frame_stack_init (struct ftw_stack *ftwst, const struct STRUCT_STAT *st)\n>> -{\n>> -  ftwst->num_blocks = FTW_STACK_CHUNK_BLOCKS;\n>> -  ftwst->stack = malloc (FTW_STACK_CHUNK_BLOCKS * sizeof (*ftwst->stack));\n>> -  if (ftwst->stack == NULL)\n>> -    return false;\n>> -\n>> -  ftwst->stack[0] = malloc (FTW_STACK_CHUNK_SIZE * sizeof (struct ftw_frame));\n>> -  if (ftwst->stack[0] == NULL)\n>> -    {\n>> -      free (ftwst->stack);\n>> -      return false;\n>> -    }\n>> -\n>> -  ftwst->top = 0;\n>> -  frame_stack_reset_top (ftwst, st);\n>> -  return true;\n>> -}\n>> -\n>> -static void\n>> -frame_stack_free (struct ftw_stack *ftwst)\n>> -{\n>> -  for (size_t i = 0; i < ftwst->num_blocks; i++)\n>> -    free (ftwst->stack[i]);\n>> -  free (ftwst->stack);\n>> -}\n>> -\n>> -static bool\n>> -frame_stack_add (struct ftw_stack *ftwst, const struct STRUCT_STAT *st)\n>> -{\n>> -  if (ftwst->top + 1 >= ftwst->num_blocks * FTW_STACK_CHUNK_SIZE)\n>> -    {\n>> -      size_t new_blocks = ftwst->num_blocks + 1;\n>> -      struct ftw_frame **new_stack = realloc (\n>> -\t  ftwst->stack, new_blocks * sizeof (*ftwst->stack));\n>> -\n>> -      if (new_stack == NULL)\n>> -\treturn false;\n>> -      ftwst->stack = new_stack;\n>> -      ftwst->stack[ftwst->num_blocks] = malloc (\n>> -\t  FTW_STACK_CHUNK_SIZE * sizeof (struct ftw_frame));\n>> -      if (ftwst->stack[ftwst->num_blocks] == NULL)\n>> -\treturn false;\n>> -      ftwst->num_blocks = new_blocks;\n>> -    }\n>> -  ftwst->top++;\n>> -  frame_stack_reset_top (ftwst, st);\n>> -  return true;\n>> -}\n>> -\n>> -static void\n>> -frame_closedir (struct ftw_data *data, struct ftw_frame *frame)\n>> -{\n>> -  int save_err = errno;\n>> -  assert (frame->dir.content == NULL);\n>> -  __closedir (frame->dir.stream);\n>> -  frame->dir.streamfd = -1;\n>> -  __set_errno (save_err);\n>> -  if (data->actdir-- == 0)\n>> -    data->actdir = data->maxdir - 1;\n>> -  data->dirstreams[data->actdir] = NULL;\n>> -  frame->dir.stream = NULL;\n>> -}\n>> -\n>> -static int\n>> -ftw_dir (struct ftw_data *data, const struct STRUCT_STAT *st)\n>> -{\n>> -  struct ftw_stack ftwst;\n>> -  if (!frame_stack_init (&ftwst, st))\n>> -    return -1;\n>> -\n>> -  int result = 0;\n>> -\n>> -  while (ftwst.top >= 0)\n>> -    {\n>> -      struct ftw_frame *frame = frame_stack_get (&ftwst, 0);\n>> -      struct dir_data *old_dir = (ftwst.top > 0)\n>> -\t? &frame_stack_get (&ftwst, -1)->dir : NULL;\n>> -\n>> -      if (frame->state == FTW_STATE_INIT)\n>> -\t{\n>> -\t  frame->previous_base = data->ftw.base;\n>> -\t  result = open_dir_stream (\n>> -\t      old_dir == NULL ? NULL : &old_dir->streamfd, data, &frame->dir);\n>> -\t  if (result != 0)\n>> -\t    {\n>> -\t      if (errno == EACCES)\n>> -\t\tresult = CALL_FUNC (data, data->dirbuf, &frame->st, FTW_DNR,\n>> -\t\t\t\t    &data->ftw);\n>> -\t      ftwst.top--;\n>> -\t      /* Intercept FTW_SKIP_SUBTREE when popping frame */\n>> -\t      if (ftwst.top >= 0 && (data->flags & FTW_ACTIONRETVAL)\n>> -\t\t  && result == FTW_SKIP_SUBTREE)\n>> -\t\tresult = 0;\n>> -\t      continue;\n>> -\t    }\n>> -\n>> -\t  if (!(data->flags & FTW_DEPTH))\n>> -\t    {\n>> -\t      result = CALL_FUNC (data, data->dirbuf, &frame->st, FTW_D,\n>> -\t\t\t\t  &data->ftw);\n>> -\t      if (result != 0)\n>> -\t\tgoto state0_fail;\n>> -\t    }\n>> -\n>> -\t  if (data->flags & FTW_CHDIR)\n>> -\t    {\n>> -\t      if (__fchdir (__dirfd (frame->dir.stream)) < 0)\n>> -\t\t{\n>> -\t\t  result = -1;\n>> -\t\tstate0_fail:\n>> -\t\t  frame_closedir (data, frame);\n>> -\t\t  ftwst.top--;\n>> -\t\t  /* Intercept FTW_SKIP_SUBTREE when popping frame.  */\n>> -\t\t  if (ftwst.top >= 0 && (data->flags & FTW_ACTIONRETVAL)\n>> -\t\t      && result == FTW_SKIP_SUBTREE)\n>> -\t\t    result = 0;\n>> -\t\t  continue;\n>> -\t\t}\n>> -\t    }\n>> -\n>> -\t  ++data->ftw.level;\n>> -\t  char *startp = strchr (data->dirbuf, '\\0');\n>> -\t  assert (startp != data->dirbuf);\n>> -\t  if (startp[-1] != '/')\n>> -\t    *startp++ = '/';\n>> -\t  data->ftw.base = startp - data->dirbuf;\n>> -\n>> -\t  frame->state = FTW_STATE_STREAM_LOOP;\n>> -\t  frame->runp = frame->dir.content;\n>> -\t}\n>> -      else if (frame->state == FTW_STATE_STREAM_LOOP)\n>> -\t{\n>> -\t  if (result != 0)\n>> -\t    {\n>> -\t      frame->state = FTW_STATE_CLEANUP;\n>> -\t      continue;\n>> -\t    }\n>> -\n>> -\t  if (frame->dir.stream == NULL)\n>> -\t    {\n>> -\t      frame->state = FTW_STATE_CONTENT_LOOP;\n>> -\t      frame->runp = frame->dir.content;\n>> -\t      continue;\n>> -\t    }\n>> -\n>> -\t  struct dirent64 *d = __readdir64 (frame->dir.stream);\n>> -\t  if (d != NULL)\n>> -\t    {\n>> -\t      struct STRUCT_STAT child_st;\n>> -\t      bool descend = false;\n>> -\t      result = process_entry (data, &frame->dir, d->d_name, NAMLEN (d),\n>> -\t\t\t\t      &child_st, &descend);\n>> -\n>> -\t      if (result == 0 && descend)\n>> -\t\t{\n>> -\t\t  if (!frame_stack_add (&ftwst, &child_st))\n>> -\t\t    {\n>> -\t\t      result = -1;\n>> -\t\t      frame->state = FTW_STATE_CLEANUP;\n>> -\t\t    }\n>> -\t\t  continue;\n>> -\t\t}\n>> -\t      else if (result != 0)\n>> -\t\t{\n>> -\t\t  frame->state = FTW_STATE_CLEANUP;\n>> -\t\t  continue;\n>> -\t\t}\n>> -\t    }\n>> -\t  else\n>> -\t    frame->state = FTW_STATE_CLEANUP;\n>> -\t}\n>> -      else if (frame->state == FTW_STATE_CONTENT_LOOP)\n>> -\t{\n>> -\t  /* Check if we are safely positioned to process the starting path.\n>> -\t     The 'result' variable here comes from one of two places:\n>> -\n>> -\t     1. Initialization: defaults to 0 at the top of ftw_startup.  If\n>> -\t        the FTW_CHDIR flag was NOT passed, it remains 0, meaning we\n>> -\t\tare good to go.\n>> -\n>> -\t     2. Directory Change: If FTW_CHDIR WAS passed, 'result' holds the\n>> -\t        return value of the preceding __chdir() call (either moving\n>> -\t\tto \"/\" or the parsed base directory).\n>> -\n>> -\t     If 'result' is 0, the setup succeeded (or wasn't needed) and we\n>> -\t     can safely stat the initial object.  Othewise, the chdir failed,\n>> -\t     so we skip processing and fall through to the cleanup phase.  */\n>> -\t  if (result != 0)\n>> -\t    {\n>> -\t      frame->state = FTW_STATE_CLEANUP;\n>> -\t      continue;\n>> -\t    }\n>> -\n>> -\t  if (frame->runp != NULL && *frame->runp != '\\0')\n>> -\t    {\n>> -\t      char *endp = strchr (frame->runp, '\\0');\n>> -\t      struct STRUCT_STAT child_st;\n>> -\t      bool descend = false;\n>> -\n>> -\t      result = process_entry (data, &frame->dir, frame->runp,\n>> -\t\t\t\t      endp - frame->runp, &child_st,\n>> -\t\t\t\t      &descend);\n>> -\t      frame->runp = endp + 1;\n>> -\n>> -\t      if (result == 0 && descend)\n>> -\t\t{\n>> -\t\t  if (!frame_stack_add (&ftwst, &child_st))\n>> -\t\t    {\n>> -\t\t      result = -1;\n>> -\t\t      frame->state = FTW_STATE_CLEANUP;\n>> -\t\t    }\n>> -\t\t  continue;\n>> -\t\t}\n>> -\t      else if (result != 0)\n>> -\t\t{\n>> -\t\t  frame->state = FTW_STATE_CLEANUP;\n>> -\t\t  continue;\n>> -\t\t}\n>> -\t    }\n>> -\t  else\n>> -\t    frame->state = FTW_STATE_CLEANUP;\n>> -\t}\n>> -      else if (frame->state == FTW_STATE_CLEANUP)\n>> -\t{\n>> -\t  if (frame->dir.stream != NULL)\n>> -\t    frame_closedir (data, frame);\n>> -\t  else if (frame->dir.content != NULL)\n>> -\t    {\n>> -\t      free (frame->dir.content);\n>> -\t      frame->dir.content = NULL;\n>> -\t    }\n>> -\n>> -\t  if ((data->flags & FTW_ACTIONRETVAL) && result == FTW_SKIP_SIBLINGS)\n>> -\t    result = 0;\n>> -\n>> -\t  data->dirbuf[data->ftw.base - 1] = '\\0';\n>> -\t  --data->ftw.level;\n>> -\t  data->ftw.base = frame->previous_base;\n>> -\n>> -\t  if (result == 0 && (data->flags & FTW_DEPTH))\n>> -\t    result\n>> -\t\t= CALL_FUNC (data, data->dirbuf, &frame->st, FTW_DP,\n>> -\t\t\t     &data->ftw);\n>> -\n>> -\t  if (old_dir != NULL && (data->flags & FTW_CHDIR)\n>> -\t      && (result == 0\n>> -\t\t  || ((data->flags & FTW_ACTIONRETVAL)\n>> -\t\t      && (result != -1 && result != FTW_STOP))))\n>> -\t    {\n>> -\t      int done = 0;\n>> -\t      if (old_dir->stream != NULL)\n>> -\t\tif (__fchdir (__dirfd (old_dir->stream)) == 0)\n>> -\t\t  done = 1;\n>> -\n>> -\t      if (!done)\n>> -\t\t{\n>> -\t\t  if (data->ftw.base == 1)\n>> -\t\t    {\n>> -\t\t      if (__chdir (\"/\") < 0)\n>> -\t\t\tresult = -1;\n>> -\t\t    }\n>> -\t\t  else if (__chdir (\"..\") < 0)\n>> -\t\t    result = -1;\n>> -\t\t}\n>> -\t    }\n>> -\n>> -\t  ftwst.top--;\n>> -\t  /* Intercept FTW_SKIP_SUBTREE when popping frame.  */\n>> -\t  if (ftwst.top >= 0 && (data->flags & FTW_ACTIONRETVAL)\n>> -\t      && result == FTW_SKIP_SUBTREE)\n>> -\t    result = 0;\n>> -\t}\n>> -    }\n>> -\n>> -  frame_stack_free (&ftwst);\n>> -\n>> -  return result;\n>> -}\n>> -\n>> -\n>> -static int\n>> -ftw_startup (const char *dir, bool is_nftw, func_callback_t func,\n>> -\t     int descriptors, int flags)\n>> -{\n>> -  struct ftw_data data = { .dirstreams = NULL };\n>> -  struct STRUCT_STAT st;\n>> -  int result = 0;\n>> -  int cwdfd = -1;\n>> -  char *cwd = NULL;\n>> -  char *cp;\n>> -\n>> -  /* First make sure the parameters are reasonable.  */\n>> -  if (dir[0] == '\\0')\n>> -    {\n>> -      __set_errno (ENOENT);\n>> -      return -1;\n>> -    }\n>> -\n>> -  data.maxdir = descriptors < 1 ? 1 : descriptors;\n>> -  data.actdir = 0;\n>> -  /* PATH_MAX is always defined when we get here.  */\n>> -  if (!ftw_allocate (&data, MAX (2 * strlen (dir), PATH_MAX)))\n>> -    return -1;\n>> -  memset (data.dirstreams, '\\0', data.maxdir * sizeof (struct dir_data *));\n>> -  cp = __stpcpy (data.dirbuf, dir);\n>> -  /* Strip trailing slashes.  */\n>> -  while (cp > data.dirbuf + 1 && cp[-1] == '/')\n>> -    --cp;\n>> -  *cp = '\\0';\n>> -\n>> -  data.ftw.level = 0;\n>> -\n>> -  /* Find basename.  */\n>> -  while (cp > data.dirbuf && cp[-1] != '/')\n>> -    --cp;\n>> -  data.ftw.base = cp - data.dirbuf;\n>> -\n>> -  data.flags = flags;\n>> -\n>> -  data.is_nftw = is_nftw;\n>> -  data.func = func;\n>> -\n>> -  /* Since we internally use the complete set of FTW_* values we need\n>> -     to reduce the value range before calling a `ftw' callback.  */\n>> -  data.cvt_arr = is_nftw ? nftw_arr : ftw_arr;\n>> -\n>> -  /* No object known so far.  */\n>> -  data.known_objects = NULL;\n>> -\n>> -  /* Now go to the directory containing the initial file/directory.  */\n>> -  if (flags & FTW_CHDIR)\n>> -    {\n>> -      /* We have to be able to go back to the current working\n>> -\t directory.  The best way to do this is to use a file\n>> -\t descriptor.  */\n>> -      cwdfd = __open (\".\", O_RDONLY | O_DIRECTORY);\n>> -      if (cwdfd == -1)\n>> -\t{\n>> -\t  /* Try getting the directory name.  This can be needed if\n>> -\t     the current directory is executable but not readable.  */\n>> -\t  if (errno == EACCES)\n>> -\t    /* GNU extension ahead.  */\n>> -\t    cwd =  __getcwd (NULL, 0);\n>> -\n>> -\t  if (cwd == NULL)\n>> -\t    goto out_fail;\n>> -\t}\n>> -      else if (data.maxdir > 1)\n>> -\t/* Account for the file descriptor we use here.  */\n>> -\t--data.maxdir;\n>> -\n>> -      if (data.ftw.base > 0)\n>> -\t{\n>> -\t  /* Change to the directory the file is in.  In data.dirbuf\n>> -\t     we have a writable copy of the file name.  Just NUL\n>> -\t     terminate it for now and change the directory.  */\n>> -\t  if (data.ftw.base == 1)\n>> -\t    /* I.e., the file is in the root directory.  */\n>> -\t    result = __chdir (\"/\");\n>> -\t  else\n>> -\t    {\n>> -\t      char ch = data.dirbuf[data.ftw.base - 1];\n>> -\t      data.dirbuf[data.ftw.base - 1] = '\\0';\n>> -\t      result = __chdir (data.dirbuf);\n>> -\t      data.dirbuf[data.ftw.base - 1] = ch;\n>> -\t    }\n>> -\t}\n>> -    }\n>> -\n>> -  /* Get stat info for start directory.  */\n>> -  if (result == 0)\n>> -    {\n>> -      const char *name;\n>> -\n>> -      if (data.flags & FTW_CHDIR)\n>> -\t{\n>> -\t  name = data.dirbuf + data.ftw.base;\n>> -\t  if (name[0] == '\\0')\n>> -\t    name = \".\";\n>> -\t}\n>> -      else\n>> -\tname = data.dirbuf;\n>> -\n>> -      if (((flags & FTW_PHYS)\n>> -\t   ? LSTAT (name, &st)\n>> -\t   : STAT (name, &st)) < 0)\n>> -\t{\n>> -\t  if (!(flags & FTW_PHYS)\n>> -\t      && errno == ENOENT\n>> -\t      && LSTAT (name, &st) == 0\n>> -\t      && S_ISLNK (st.st_mode))\n>> -\t    result = CALL_FUNC (&data, data.dirbuf, &st, data.cvt_arr[FTW_SLN],\n>> -\t\t\t\t&data.ftw);\n>> -\t  else\n>> -\t    /* No need to call the callback since we cannot say anything\n>> -\t       about the object.  */\n>> -\t    result = -1;\n>> -\t}\n>> -      else\n>> -\t{\n>> -\t  if (S_ISDIR (st.st_mode))\n>> -\t    {\n>> -\t      /* Remember the device of the initial directory in case\n>> -\t\t FTW_MOUNT is given.  */\n>> -\t      data.dev = st.st_dev;\n>> -\n>> -\t      /* We know this directory now.  */\n>> -\t      if (!(flags & FTW_PHYS))\n>> -\t\tresult = add_object (&data, &st);\n>> -\n>> -\t      if (result == 0)\n>> -\t\tresult = ftw_dir (&data, &st);\n>> -\t    }\n>> -\t  else\n>> -\t    {\n>> -\t      int flag = S_ISLNK (st.st_mode) ? FTW_SL : FTW_F;\n>> -\n>> -\t      result = CALL_FUNC (&data, data.dirbuf, &st, data.cvt_arr[flag],\n>> -\t\t\t\t  &data.ftw);\n>> -\t    }\n>> -\t}\n>> -\n>> -      if ((flags & FTW_ACTIONRETVAL)\n>> -\t  && (result == FTW_SKIP_SUBTREE || result == FTW_SKIP_SIBLINGS))\n>> -\tresult = 0;\n>> -    }\n>> -\n>> -  /* Return to the start directory (if necessary).  */\n>> -  if (cwdfd != -1)\n>> -    {\n>> -      int save_err = errno;\n>> -      __fchdir (cwdfd);\n>> -      __close_nocancel_nostatus (cwdfd);\n>> -      __set_errno (save_err);\n>> -    }\n>> -  else if (cwd != NULL)\n>> -    {\n>> -      int save_err = errno;\n>> -      __chdir (cwd);\n>> -      free (cwd);\n>> -      __set_errno (save_err);\n>> -    }\n>> -\n>> -  /* Free all memory.  */\n>> - out_fail:\n>> -  __tdestroy (data.known_objects, free);\n>> -  free (data.dirstreams);\n>> -\n>> -  return result;\n>> -}\n>> -\n>> -\n>> -\n>> -/* Entry points.  */\n>> -\n>> -int\n>> -FTW_NAME (const char *path, FTW_FUNC_T func, int descriptors)\n>> -{\n>> -  return ftw_startup (path, false, (func_callback_t) { .ftw_func = func },\n>> -\t\t      descriptors, 0);\n>> -}\n>> -\n>> -#ifndef NFTW_OLD_NAME\n>> -int\n>> -NFTW_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags)\n>> -{\n>> -  return ftw_startup (path, true, (func_callback_t) { .nftw_func = func },\n>> -\t\t      descriptors, flags);\n>> -}\n>> -#else\n>> -\n>> -# include <shlib-compat.h>\n>> -\n>> -int NFTW_NEW_NAME (const char *, NFTW_FUNC_T, int, int);\n>> -\n>> -int\n>> -NFTW_NEW_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags)\n>> -{\n>> -  if (flags\n>> -      & ~(FTW_PHYS | FTW_MOUNT | FTW_CHDIR | FTW_DEPTH | FTW_ACTIONRETVAL))\n>> -    {\n>> -      __set_errno (EINVAL);\n>> -      return -1;\n>> -    }\n>> -  return ftw_startup (path, true, (func_callback_t) { .nftw_func = func },\n>> -\t\t      descriptors, flags);\n>> -}\n>> -versioned_symbol (libc, NFTW_NEW_NAME, NFTW_NAME, GLIBC_2_3_3);\n>> -\n>> -# if SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_3_3)\n>> -\n>> -/* Older nftw* version just ignored all unknown flags.  */\n>> -\n>> -int NFTW_OLD_NAME (const char *, NFTW_FUNC_T, int, int);\n>> -\n>> -int\n>> -attribute_compat_text_section\n>> -NFTW_OLD_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags)\n>> -{\n>> -  flags &= (FTW_PHYS | FTW_MOUNT | FTW_CHDIR | FTW_DEPTH);\n>> -  return ftw_startup (path, true, (func_callback_t) { .nftw_func = func },\n>> -\t\t      descriptors, flags);\n>> -}\n>> -\n>> -compat_symbol (libc, NFTW_OLD_NAME, NFTW_NAME, GLIBC_2_1);\n>> -# endif\n>> -#endif /* NFTW_OLD_NAME  */\n>> diff --git a/io/ftw64-time64.c b/io/ftw64-time64.c\n>> index 2df871f802..88f58fd85c 100644\n>> --- a/io/ftw64-time64.c\n>> +++ b/io/ftw64-time64.c\n>> @@ -29,5 +29,5 @@\n>>   # define FTW_FUNC_T     __ftw64_time64_func_t\n>>   # define NFTW_FUNC_T    __nftw64_time64_func_t\n>>   \n>> -# include \"ftw.c\"\n>> +# include \"ftw-common.c\"\n>>   #endif\n>> diff --git a/io/ftw64.c b/io/ftw64.c\n>> index 0d7cb30091..d3cd14c21a 100644\n>> --- a/io/ftw64.c\n>> +++ b/io/ftw64.c\n>> @@ -16,8 +16,8 @@\n>>      License along with the GNU C Library; if not, see\n>>      <https://www.gnu.org/licenses/>.  */\n>>   \n>> -#define FTW_NAME ftw64\n>> -#define NFTW_NAME nftw64\n>> +#define FTW_NAME __ftw64\n>> +#define NFTW_NAME __nftw64\n>>   #define NFTW_OLD_NAME __old_nftw64\n>>   #define NFTW_NEW_NAME __new_nftw64\n>>   #define INO_T ino64_t\n>> @@ -28,4 +28,26 @@\n>>   #define FTW_FUNC_T __ftw64_func_t\n>>   #define NFTW_FUNC_T __nftw64_func_t\n>>   \n>> -#include \"ftw.c\"\n>> +#define ftw __rename_ftw\n>> +#define nftw __rename_nftw\n>> +\n>> +#include <shlib-compat.h>\n>> +#include \"ftw-common.c\"\n>> +\n>> +#undef ftw\n>> +#undef nftw\n>> +\n>> +weak_alias (__ftw64, ftw64)\n>> +versioned_symbol (libc, __new_nftw64, nftw64, GLIBC_2_3_3);\n>> +\n>> +#if SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_3_3)\n>> +compat_symbol (libc, __old_nftw64, nftw64, GLIBC_2_1);\n>> +#endif\n>> +\n>> +#ifdef __OFF_T_MATCHES_OFF64_T\n>> +weak_alias (__ftw64, ftw)\n>> +versioned_symbol (libc, __new_nftw64, nftw, GLIBC_2_3_3);\n>> +# if SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_3_3)\n>> +compat_symbol (libc, __old_nftw64, nftw, GLIBC_2_1);\n>> +# endif\n>> +#endif\n>> diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw.c b/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw.c\n>> deleted file mode 100644\n>> index 46389568b2..0000000000\n>> --- a/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw.c\n>> +++ /dev/null\n>> @@ -1 +0,0 @@\n>> -#include <io/ftw.c>\n>> diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw64.c b/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw64.c\n>> deleted file mode 100644\n>> index cb02172b3e..0000000000\n>> --- a/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw64.c\n>> +++ /dev/null\n>> @@ -1 +0,0 @@\n>> -#include <io/ftw64.c>\n>> diff --git a/sysdeps/unix/sysv/linux/x86_64/x32/ftw.c b/sysdeps/unix/sysv/linux/x86_64/x32/ftw.c\n>> deleted file mode 100644\n>> index a21dfe5690..0000000000\n>> --- a/sysdeps/unix/sysv/linux/x86_64/x32/ftw.c\n>> +++ /dev/null\n>> @@ -1 +0,0 @@\n>> -#include <sysdeps/wordsize-64/ftw.c>\n>> diff --git a/sysdeps/unix/sysv/linux/x86_64/x32/ftw64.c b/sysdeps/unix/sysv/linux/x86_64/x32/ftw64.c\n>> deleted file mode 100644\n>> index 3c025b738a..0000000000\n>> --- a/sysdeps/unix/sysv/linux/x86_64/x32/ftw64.c\n>> +++ /dev/null\n>> @@ -1 +0,0 @@\n>> -#include <sysdeps/wordsize-64/ftw64.c>\n>> diff --git a/sysdeps/wordsize-64/ftw.c b/sysdeps/wordsize-64/ftw.c\n>> deleted file mode 100644\n>> index ca19903799..0000000000\n>> --- a/sysdeps/wordsize-64/ftw.c\n>> +++ /dev/null\n>> @@ -1,16 +0,0 @@\n>> -#define ftw64 __rename_ftw64\n>> -#define nftw64 __rename_nftw64\n>> -\n>> -#include \"../../io/ftw.c\"\n>> -\n>> -#undef ftw64\n>> -#undef nftw64\n>> -\n>> -weak_alias (ftw, ftw64)\n>> -strong_alias (__new_nftw, __new_nftw64)\n>> -versioned_symbol (libc, __new_nftw64, nftw64, GLIBC_2_3_3);\n>> -\n>> -#if SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_3_3)\n>> -strong_alias (__old_nftw, __old_nftw64)\n>> -compat_symbol (libc, __old_nftw64, nftw64, GLIBC_2_1);\n>> -#endif\n>> diff --git a/sysdeps/wordsize-64/ftw64.c b/sysdeps/wordsize-64/ftw64.c\n>> deleted file mode 100644\n>> index 1cfcaadfd1..0000000000\n>> --- a/sysdeps/wordsize-64/ftw64.c\n>> +++ /dev/null\n>> @@ -1 +0,0 @@\n>> -/* Defined in ftw.c.  */\n>","headers":{"Return-Path":"<libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org>","X-Original-To":["incoming@patchwork.ozlabs.org","libc-alpha@sourceware.org"],"Delivered-To":["patchwork-incoming@legolas.ozlabs.org","libc-alpha@sourceware.org"],"Authentication-Results":["legolas.ozlabs.org;\n\tdkim=pass (1024-bit key;\n unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256\n header.s=mimecast20190719 header.b=fHnbXOL0;\n\tdkim-atps=neutral","legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=sourceware.org\n (client-ip=2620:52:6:3111::32; helo=vm01.sourceware.org;\n envelope-from=libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org;\n receiver=patchwork.ozlabs.org)","sourceware.org;\n\tdkim=pass (1024-bit key,\n unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256\n header.s=mimecast20190719 header.b=fHnbXOL0","sourceware.org; dmarc=pass (p=quarantine dis=none)\n header.from=redhat.com","sourceware.org; spf=pass smtp.mailfrom=redhat.com","server2.sourceware.org;\n arc=none smtp.remote-ip=170.10.129.124"],"Received":["from vm01.sourceware.org (vm01.sourceware.org\n [IPv6:2620:52:6:3111::32])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4fy5bk5pD9z1yD3\n\tfor <incoming@patchwork.ozlabs.org>; Sat, 18 Apr 2026 06:10:42 +1000 (AEST)","from vm01.sourceware.org (localhost [127.0.0.1])\n\tby sourceware.org (Postfix) with ESMTP id EF4DB4D108EF\n\tfor <incoming@patchwork.ozlabs.org>; Fri, 17 Apr 2026 20:10:40 +0000 (GMT)","from us-smtp-delivery-124.mimecast.com\n (us-smtp-delivery-124.mimecast.com [170.10.129.124])\n by sourceware.org (Postfix) with ESMTP id 426974D108D5\n for <libc-alpha@sourceware.org>; Fri, 17 Apr 2026 20:10:11 +0000 (GMT)","from mail-qt1-f200.google.com (mail-qt1-f200.google.com\n [209.85.160.200]) by relay.mimecast.com with ESMTP with STARTTLS\n (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id\n us-mta-101-kdXTCycSPKO6sFjRFQspqw-1; Fri, 17 Apr 2026 16:10:09 -0400","by mail-qt1-f200.google.com with SMTP id\n d75a77b69052e-50d826ed6f9so52449301cf.1\n for <libc-alpha@sourceware.org>; Fri, 17 Apr 2026 13:10:09 -0700 (PDT)","from [192.168.0.116] ([198.48.244.52])\n by smtp.gmail.com with ESMTPSA id\n d75a77b69052e-50e39495192sm19970231cf.27.2026.04.17.13.10.05\n (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128);\n Fri, 17 Apr 2026 13:10:05 -0700 (PDT)"],"DKIM-Filter":["OpenDKIM Filter v2.11.0 sourceware.org EF4DB4D108EF","OpenDKIM Filter v2.11.0 sourceware.org 426974D108D5"],"DMARC-Filter":"OpenDMARC Filter v1.4.2 sourceware.org 426974D108D5","ARC-Filter":"OpenARC Filter v1.0.0 sourceware.org 426974D108D5","ARC-Seal":"i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1776456611; cv=none;\n b=Ptx3o3O0INbn2pxFRh1+PidN2NoprAJstmRMChdEVQCeKC+mL6ZSXa5D1Qyna762JPQ3qYgz6trGnwet5s0axjwhRom7RmNNArWv64Xpqe8CmilG6sLXgNzg6RoI8Y447s0fHGab/VDeery8ccW6WMYewi0pTsRrckahw5jdSJo=","ARC-Message-Signature":"i=1; a=rsa-sha256; d=sourceware.org; s=key;\n t=1776456611; c=relaxed/simple;\n bh=Jz5utSAqAxv0cl/xo8BucnQPPtXoWJCyyQGgvA5Bfho=;\n h=DKIM-Signature:Message-ID:Date:MIME-Version:Subject:To:From;\n b=R2veeQAsIo4Br2DzbRLKelb+oNPLH50jrVrMDhfdtGig0o8yS/DO3mSiF9GmCygp3EezjUgyEDxU7VwqUPthE7KPjK3UFmCaO2ikqY61+oz5vdfF3ywEhKz/rr1SH9wI5dfPtDBqWZKnhz7j7W8IQ1i/bdZE92TdSPzE7cWI1vg=","ARC-Authentication-Results":"i=1; server2.sourceware.org","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n s=mimecast20190719; t=1776456610;\n h=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n to:to:cc:cc:mime-version:mime-version:content-type:content-type:\n content-transfer-encoding:content-transfer-encoding:\n in-reply-to:in-reply-to:references:references;\n bh=5YIToz1xzqZ3M4o0cYr3YLXSp30/94mYSxYfwRvHY8k=;\n b=fHnbXOL0Odf3mckE0tCbTQam5YJn/nXp0jBdjEjb91DpRCPIxCzVWEDGA3PWKI9eREvP3N\n nTCNpjt0CSOlrQJsCBiei9rMfEMkpiKwTJk43HfsCS+2SCTCXnld7C+SAwVWgJ2rwdlUj+\n Z5s258t4Sj6AbUOPQ/XYxSsSOOvJ3hI=","X-MC-Unique":"kdXTCycSPKO6sFjRFQspqw-1","X-Mimecast-MFC-AGG-ID":"kdXTCycSPKO6sFjRFQspqw_1776456609","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1776456608; x=1777061408;\n h=content-transfer-encoding:in-reply-to:organization:from\n :content-language:references:cc:to:subject:user-agent:mime-version\n :date:message-id:x-gm-gg:x-gm-message-state:from:to:cc:subject:date\n :message-id:reply-to;\n bh=5YIToz1xzqZ3M4o0cYr3YLXSp30/94mYSxYfwRvHY8k=;\n b=oUbwTl0F8RsdK8o4ureXDHXCIERXqS8q6WAHYM9dKAgpyry8cc22RMCMKo51g86YUa\n gzWZItx3yhn2Tka33bW1Ix1FOJS7ZsCOKK5R3OG1lVqtwj90SvqHiQB6aUiWFx+N/cIi\n +pZVS6UzuYDpGQCxcmSf+FTfkOtjqR26SczGA4SHFrxFYt811TVU3cGN8sjO5iUKl/kl\n J7Ktsj3eEvoMisN1Z2Ml6EbkX1QoHmbisLvxNZgC76eySXzc0ys5ud/QTd/66hiSisKL\n 2jPG++fJ0vmHaslsLyPV3rAZId0Y+RB5eMDzTiBGi7BUWevMVUAFDYAY029yag6oUlsn\n eVMg==","X-Forwarded-Encrypted":"i=1;\n AFNElJ8OyTCRmsXOans3lXYyxq/hU1WQw6eDe+sX7sKHWYtDm9cV8gL+HBSUfiQ5m9mDedrXo0zwt1N203Vy@sourceware.org","X-Gm-Message-State":"AOJu0YzBAo4rZt/wuWDI1ITetoMLTmgo+CKKIG/tdWafL7hij4bUdVyY\n jjTCwxpebDSQmlFJLtk1paZ6JLd1++2eprPydR4SHLZ5poApM2t5KiKmgTCmGQW8DGfVQtHJ8bG\n pySKgFBsTnfQ28YYcaAN5Yrt28bfAuOGRzwDwOYBbwltnKyIhqybvxXOKfBUExjRYuctl8A==","X-Gm-Gg":"AeBDievFiia7empUPVtppQEEF5qmwVOQBEpB69xzucqoUAhUJc5m97LMThi71IKIwdt\n 9KBDrvNbh5IJPZtspX2Bd87mmyC6KFoaQIJ3jDdxLGiixc+92lZ/GkDpkQs6/wRH/OK2h6NjktS\n HoVQqz8EnQew+Z7q05msk2sWYzEFC7iC99QblQZ6WylvmbHcXbm/kVpB2r7TEQrHQR1ChmOP5Fu\n 4C+e2nbQdownIbLkqQTxKZpXhPDpfwS1JVPDEOFriAQGD5ID7POxZbsMI32K/QRgFDjCVo6g9vw\n hzL/XJmkn2n3d3YsPlEeRfaSm54f/dQBvqZpRhblE8sMfQPuQr8xV7b7jNFoe6VmvheXEmGDpPM\n CNFzyd4MVxtNcwtgJJkt7qHqVcNsSej9k9Fum3IN9wac0Wh6eYr47Oz5pYsRBrhEa//tT08m82w\n raZyGxYGOzTVY839/M6e1OHbVn6uofTV6+","X-Received":["by 2002:a05:622a:283:b0:50b:2d6e:5253 with SMTP id\n d75a77b69052e-50e367d1108mr55231531cf.41.1776456607576;\n Fri, 17 Apr 2026 13:10:07 -0700 (PDT)","by 2002:a05:622a:283:b0:50b:2d6e:5253 with SMTP id\n d75a77b69052e-50e367d1108mr55230601cf.41.1776456606491;\n Fri, 17 Apr 2026 13:10:06 -0700 (PDT)"],"Message-ID":"<a7163076-2196-4180-bbef-da7bc94c119e@redhat.com>","Date":"Fri, 17 Apr 2026 16:10:04 -0400","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH 2/3] io: Consolidate ftw implementation","To":"Adhemerval Zanella Netto <adhemerval.zanella@linaro.org>,\n libc-alpha@sourceware.org","Cc":"Collin Funk <collin.funk1@gmail.com>, Paul Eggert <eggert@cs.ucla.edu>","References":"<20260417132808.235562-1-adhemerval.zanella@linaro.org>\n <20260417132808.235562-3-adhemerval.zanella@linaro.org>\n <3aa40b5a-25c2-4640-aef2-17722fe59154@linaro.org>","From":"Carlos O'Donell <carlos@redhat.com>","Organization":"Red Hat, LLC.","In-Reply-To":"<3aa40b5a-25c2-4640-aef2-17722fe59154@linaro.org>","X-Mimecast-Spam-Score":"0","X-Mimecast-MFC-PROC-ID":"PDitKUKdQoRk8OYAKtLT1-d2UloU_-PQxYyKTYVAavE_1776456609","X-Mimecast-Originator":"redhat.com","Content-Language":"en-US","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"7bit","X-BeenThere":"libc-alpha@sourceware.org","X-Mailman-Version":"2.1.30","Precedence":"list","List-Id":"Libc-alpha mailing list <libc-alpha.sourceware.org>","List-Unsubscribe":"<https://sourceware.org/mailman/options/libc-alpha>,\n <mailto:libc-alpha-request@sourceware.org?subject=unsubscribe>","List-Archive":"<https://sourceware.org/pipermail/libc-alpha/>","List-Post":"<mailto:libc-alpha@sourceware.org>","List-Help":"<mailto:libc-alpha-request@sourceware.org?subject=help>","List-Subscribe":"<https://sourceware.org/mailman/listinfo/libc-alpha>,\n <mailto:libc-alpha-request@sourceware.org?subject=subscribe>","Errors-To":"libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org"}}]