Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.2/patches/2224506/?format=api
{ "id": 2224506, "url": "http://patchwork.ozlabs.org/api/1.2/patches/2224506/?format=api", "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/1.2/projects/41/?format=api", "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/1.2/people/66065/?format=api", "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/1.2/series/500340/?format=api", "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" ] }