{"id":2224506,"url":"http://patchwork.ozlabs.org/api/patches/2224506/?format=json","web_url":"http://patchwork.ozlabs.org/project/glibc/patch/20260417132808.235562-3-adhemerval.zanella@linaro.org/","project":{"id":41,"url":"http://patchwork.ozlabs.org/api/projects/41/?format=json","name":"GNU C Library","link_name":"glibc","list_id":"libc-alpha.sourceware.org","list_email":"libc-alpha@sourceware.org","web_url":"","scm_url":"","webscm_url":"","list_archive_url":"","list_archive_url_format":"","commit_url_format":""},"msgid":"<20260417132808.235562-3-adhemerval.zanella@linaro.org>","list_archive_url":null,"date":"2026-04-17T13:24:56","name":"[2/3] io: Consolidate ftw implementation","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"b69b0d3427e95af7e7729c76c97698a0a524fdc1","submitter":{"id":66065,"url":"http://patchwork.ozlabs.org/api/people/66065/?format=json","name":"Adhemerval Zanella Netto","email":"adhemerval.zanella@linaro.org"},"delegate":null,"mbox":"http://patchwork.ozlabs.org/project/glibc/patch/20260417132808.235562-3-adhemerval.zanella@linaro.org/mbox/","series":[{"id":500340,"url":"http://patchwork.ozlabs.org/api/series/500340/?format=json","web_url":"http://patchwork.ozlabs.org/project/glibc/list/?series=500340","date":"2026-04-17T13:24:54","name":"Consolidate and sync fts/ftw","version":1,"mbox":"http://patchwork.ozlabs.org/series/500340/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/2224506/comments/","check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/2224506/checks/","tags":{},"related":[],"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=BsQHDGgX;\n\tdkim-atps=neutral","legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=sourceware.org\n (client-ip=38.145.34.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=BsQHDGgX","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::1233"],"Received":["from vm01.sourceware.org (vm01.sourceware.org [38.145.34.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 4fxwjp2zxpz1yCv\n\tfor <incoming@patchwork.ozlabs.org>; Fri, 17 Apr 2026 23:30:22 +1000 (AEST)","from vm01.sourceware.org (localhost [127.0.0.1])\n\tby sourceware.org (Postfix) with ESMTP id 654344CCCA0A\n\tfor <incoming@patchwork.ozlabs.org>; Fri, 17 Apr 2026 13:30:20 +0000 (GMT)","from mail-dl1-x1233.google.com (mail-dl1-x1233.google.com\n [IPv6:2607:f8b0:4864:20::1233])\n by sourceware.org (Postfix) with ESMTPS id 870814BA23DE\n for <libc-alpha@sourceware.org>; Fri, 17 Apr 2026 13:28:20 +0000 (GMT)","by mail-dl1-x1233.google.com with SMTP id\n a92af1059eb24-12713e56abdso405502c88.1\n for <libc-alpha@sourceware.org>; Fri, 17 Apr 2026 06:28:20 -0700 (PDT)","from mandiga.. ([2804:1b3:a7c3:d5d0:abc1:209f:f276:2b34])\n by smtp.gmail.com with ESMTPSA id\n a92af1059eb24-12c74a20eb5sm2556236c88.14.2026.04.17.06.28.15\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Fri, 17 Apr 2026 06:28:16 -0700 (PDT)"],"DKIM-Filter":["OpenDKIM Filter v2.11.0 sourceware.org 654344CCCA0A","OpenDKIM Filter v2.11.0 sourceware.org 870814BA23DE"],"DMARC-Filter":"OpenDMARC Filter v1.4.2 sourceware.org 870814BA23DE","ARC-Filter":"OpenARC Filter v1.0.0 sourceware.org 870814BA23DE","ARC-Seal":"i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1776432500; cv=none;\n b=pmP8S8grvAiIhxlewXl/AVg6VMsRRzHSKXALqMoil2gf07b/5mCdem9RTWFSo1Sd+hKdqLf9ukABxNIsS/Rlwqtg5oww/7uL9wAE3rH2lVw8ziWxp36LP4anpClN6Xj4xzD3REVJxtxYCuXRqT2npjID+B+Cq3LdmQ+sRuuqMMo=","ARC-Message-Signature":"i=1; a=rsa-sha256; d=sourceware.org; s=key;\n t=1776432500; c=relaxed/simple;\n bh=lf7VfjQh5RwTvlMkSu4lLtxgnL8f65Fakh+qGCPkOpo=;\n h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version;\n b=whLAiGqynTDLWfzjTvjWEE4/9uG7fIQEq4OmbmVqrExbAAV1KN1WDR4hrupQJBxZ2slASNXICLVMbHZtwj1RcrKqse3VyzuMJ3VTLeJg4euiCIA2BKzejN91kXmuI1GWWs2J/3yHbemaqyrLkQnJsu32fB02Z0zzXddXWC3iyrM=","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=1776432499; x=1777037299; darn=sourceware.org;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:from:to:cc:subject:date\n :message-id:reply-to;\n bh=vWwV8esQDxkhPoLG25I46orq+MB1VJgGFI580Z8QjMU=;\n b=BsQHDGgXCeBJH/i0/na0ZOEsZnqXblfB97r0rlaPRkDwXY5DL1l1wpb/6mQTldASxP\n Vf0sr7gYOBZ9M8XengYu+t61dkbTrJ0GtN8CIwiFekjl25aBLjA3N4pj1s9J1JYm3623\n Ze6bZe8bmKM1VGiwL/kxiH+ld7elQK4lU+iDyyEFs3fZCRIq4xeGqTxIzcjaFTNuHbFg\n RYCGe3fedE4w1peXOBjdKMLy3l4ZSTX/NUmclUqYUyDdDxdulauPcBMNIKe8WRL6kHqA\n /zhNC89hMwjT6YAj3HKKMrir0eh/KpL4EaCd+ZXxhyvmDkCu1TjMj5kIa1vS1+eVhkVU\n +P4A==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1776432499; x=1777037299;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from\n :to:cc:subject:date:message-id:reply-to;\n bh=vWwV8esQDxkhPoLG25I46orq+MB1VJgGFI580Z8QjMU=;\n b=kw0CiHfZBz0A9ubCzXZl5Eivdra3T6IEPdH+yOBCmzgdOm7jibpahBdyjQZWC0WTyW\n rCC143/Ig40B2hGVBYRXoTi57RetLyyNTdsSH6H/bOyEClW9h5XVh+rySmhEa4INgLZu\n 1DOCGhCw8j9/URZbR2rtDWlq8PY8LEZ7ZqzIaE/9kfYVZpa+kxkgfQ3kGdfsujQkkTT2\n j0PWdkW0n/JmfIaTyBW8l+OeL0VgZky6Vvfb4S5hEChwhNOGn+1w/zNYo4sQUvQm68i8\n jREv8GUs7zb2VXht93JZoaPTN3x8dAKe2crIX3EJCCoyk4z1Sc7yqn9GUODM7RDdAO11\n qvsg==","X-Gm-Message-State":"AOJu0YymcXUzjKhl56nWfobufydQNqvAnzYoAK1W+B3zMZMih3iDIaqX\n ZLyj7o+m4JKuoLR81ZIgPHuBlusPqoxtaSriHWLYk2ihBGBY52N+8feoPzDIDeIWVO8Yp0yTcMz\n ELpAJ","X-Gm-Gg":"AeBDievpthKK5Oup4EWo6Cr5DEbvNMEJJoQzyvfo8psB1BADZd/u1Cf0Q2iwM1JRdtX\n 6Oq0urjaa8cFEESxUHNTh6jsR9h8uTLpz9V15DN/H51kh02zM93hD31B9+ikO/J9hB1TRWthC22\n VX+slaIVbMBaQ9LJTLmdNXwCkhdE7XcZhDV7PH41MTinFob0Z6NoeJt24QHs0H8hQcxpepUPuok\n kbiPihqPG6T5EK8gY0errXjehIS7lAOxwSPN5SU38lVKcdtUJORnyzt3n9ctZZHu0HPMtpGLbr/\n /gND3FjgZZUruraLbSglthQbk7Egyzk/dR2NcsOQt6kl0byAlp/QT9G+aDVsesjVqzIGVIAfueR\n /eASTzkoljz0XJswjQ+GyK1SN5VX7RYYpj870ZdAwAaGGjtN6Vdit6rULM7zryxerpCsKJaeyMc\n W5ikOLqxcj+FUV45kQ9JHPYh9Gl68YmiZ288qx2wyWBIZLbw==","X-Received":"by 2002:a05:7022:6b93:b0:128:d396:f2ea with SMTP id\n a92af1059eb24-12c73b05f8emr1172304c88.11.1776432497639;\n Fri, 17 Apr 2026 06:28:17 -0700 (PDT)","From":"Adhemerval Zanella <adhemerval.zanella@linaro.org>","To":"libc-alpha@sourceware.org","Cc":"Collin Funk <collin.funk1@gmail.com>,\n\tPaul Eggert <eggert@cs.ucla.edu>","Subject":"[PATCH 2/3] io: Consolidate ftw implementation","Date":"Fri, 17 Apr 2026 10:24:56 -0300","Message-ID":"<20260417132808.235562-3-adhemerval.zanella@linaro.org>","X-Mailer":"git-send-email 2.43.0","In-Reply-To":"<20260417132808.235562-1-adhemerval.zanella@linaro.org>","References":"<20260417132808.235562-1-adhemerval.zanella@linaro.org>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","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"},"content":"Remove wordsize-64 and arch-specific implementations, for ABIs when\noff_t is the same as off64_t (__OFF_T_MATCHES_OFF64_T) the ftw64.c\nwill create the requires aliases.\n\nThe ftw.c implementation is moved to ftw-common.c to simplify\nthe __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","diff":"diff --git a/io/ftw-common.c b/io/ftw-common.c\nnew file mode 100644\nindex 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  */\ndiff --git a/io/ftw.c b/io/ftw.c\nindex 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  */\ndiff --git a/io/ftw64-time64.c b/io/ftw64-time64.c\nindex 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\ndiff --git a/io/ftw64.c b/io/ftw64.c\nindex 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\ndiff --git a/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw.c b/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw.c\ndeleted file mode 100644\nindex 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>\ndiff --git a/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw64.c b/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw64.c\ndeleted file mode 100644\nindex 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>\ndiff --git a/sysdeps/unix/sysv/linux/x86_64/x32/ftw.c b/sysdeps/unix/sysv/linux/x86_64/x32/ftw.c\ndeleted file mode 100644\nindex 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>\ndiff --git a/sysdeps/unix/sysv/linux/x86_64/x32/ftw64.c b/sysdeps/unix/sysv/linux/x86_64/x32/ftw64.c\ndeleted file mode 100644\nindex 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>\ndiff --git a/sysdeps/wordsize-64/ftw.c b/sysdeps/wordsize-64/ftw.c\ndeleted file mode 100644\nindex 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\ndiff --git a/sysdeps/wordsize-64/ftw64.c b/sysdeps/wordsize-64/ftw64.c\ndeleted file mode 100644\nindex 1cfcaadfd1..0000000000\n--- a/sysdeps/wordsize-64/ftw64.c\n+++ /dev/null\n@@ -1 +0,0 @@\n-/* Defined in ftw.c.  */\n","prefixes":["2/3"]}