get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/patches/2196379/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 2196379,
    "url": "http://patchwork.ozlabs.org/api/patches/2196379/?format=api",
    "web_url": "http://patchwork.ozlabs.org/project/glibc/patch/20260213170137.2145195-1-adhemerval.zanella@linaro.org/",
    "project": {
        "id": 41,
        "url": "http://patchwork.ozlabs.org/api/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": "<20260213170137.2145195-1-adhemerval.zanella@linaro.org>",
    "list_archive_url": null,
    "date": "2026-02-13T17:00:43",
    "name": "[v3] io: Refactor {n}ftw to use fts for stack safety and conformance (BZ 33882)",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "875c0e87c5e9dcc38e66d5b9ea05c82fc4c6df45",
    "submitter": {
        "id": 66065,
        "url": "http://patchwork.ozlabs.org/api/people/66065/?format=api",
        "name": "Adhemerval Zanella Netto",
        "email": "adhemerval.zanella@linaro.org"
    },
    "delegate": null,
    "mbox": "http://patchwork.ozlabs.org/project/glibc/patch/20260213170137.2145195-1-adhemerval.zanella@linaro.org/mbox/",
    "series": [
        {
            "id": 492118,
            "url": "http://patchwork.ozlabs.org/api/series/492118/?format=api",
            "web_url": "http://patchwork.ozlabs.org/project/glibc/list/?series=492118",
            "date": "2026-02-13T17:00:43",
            "name": "[v3] io: Refactor {n}ftw to use fts for stack safety and conformance (BZ 33882)",
            "version": 3,
            "mbox": "http://patchwork.ozlabs.org/series/492118/mbox/"
        }
    ],
    "comments": "http://patchwork.ozlabs.org/api/patches/2196379/comments/",
    "check": "pending",
    "checks": "http://patchwork.ozlabs.org/api/patches/2196379/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=wAaeE4wA;\n\tdkim-atps=neutral",
            "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=sourceware.org\n (client-ip=2620:52:6:3111::32; helo=vm01.sourceware.org;\n envelope-from=libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org;\n receiver=patchwork.ozlabs.org)",
            "sourceware.org;\n\tdkim=pass (2048-bit key,\n unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256\n header.s=google header.b=wAaeE4wA",
            "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::a33"
        ],
        "Received": [
            "from vm01.sourceware.org (vm01.sourceware.org\n [IPv6:2620:52:6:3111::32])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4fCJPX02NCz1xvQ\n\tfor <incoming@patchwork.ozlabs.org>; Sat, 14 Feb 2026 04:02:23 +1100 (AEDT)",
            "from vm01.sourceware.org (localhost [127.0.0.1])\n\tby sourceware.org (Postfix) with ESMTP id 902D34BAD158\n\tfor <incoming@patchwork.ozlabs.org>; Fri, 13 Feb 2026 17:02:21 +0000 (GMT)",
            "from mail-vk1-xa33.google.com (mail-vk1-xa33.google.com\n [IPv6:2607:f8b0:4864:20::a33])\n by sourceware.org (Postfix) with ESMTPS id A50CE4BAD14A\n for <libc-alpha@sourceware.org>; Fri, 13 Feb 2026 17:01:51 +0000 (GMT)",
            "by mail-vk1-xa33.google.com with SMTP id\n 71dfb90a1353d-560227999d2so418895e0c.1\n for <libc-alpha@sourceware.org>; Fri, 13 Feb 2026 09:01:51 -0800 (PST)",
            "from mandiga.. ([2804:1b3:a7c2:42d3:7b0c:3fb:a6d9:95d5])\n by smtp.gmail.com with ESMTPSA id\n 71dfb90a1353d-5674bfc9344sm3979626e0c.1.2026.02.13.09.01.45\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Fri, 13 Feb 2026 09:01:46 -0800 (PST)"
        ],
        "DKIM-Filter": [
            "OpenDKIM Filter v2.11.0 sourceware.org 902D34BAD158",
            "OpenDKIM Filter v2.11.0 sourceware.org A50CE4BAD14A"
        ],
        "DMARC-Filter": "OpenDMARC Filter v1.4.2 sourceware.org A50CE4BAD14A",
        "ARC-Filter": "OpenARC Filter v1.0.0 sourceware.org A50CE4BAD14A",
        "ARC-Seal": "i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1771002111; cv=none;\n b=QwuQ/2g+NcwhK8beU9ZM3RleaRK+VST8wicfHvx1+bR3V3/ugoGAmmjSdIygHAJOWxufqYmD2yg3F8nSUBeXK8guxu1c3YcLfYcurMxagB+FF3CXKOuRObwovBk0bVSwiomT4b2z+Kb4R20dH9mnrm6T5UbyH+FVrYS50cGdkwU=",
        "ARC-Message-Signature": "i=1; a=rsa-sha256; d=sourceware.org; s=key;\n t=1771002111; c=relaxed/simple;\n bh=lZV7EsoBCafyZpaZUPMYOA6Zc/niGyw/kGa7sRq/cGY=;\n h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version;\n b=S+ZR0UzTyqn5NZP71qSJAZfMzIWnAq1s6nX94qcRbohF469nQJaAuiORtrK/dPlVTyAV5VKC1QIw6l4/WXcboNlMDo0ywlkk4z6nav5oRJxskW24cgZpCYpsm6RwC1kJ1QiaVRkp01bSQ3xnwYjhcWy+psllHQv/4842ucaTz0A=",
        "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=1771002111; x=1771606911; darn=sourceware.org;\n h=content-transfer-encoding:mime-version:message-id:date:subject:cc\n :to:from:from:to:cc:subject:date:message-id:reply-to;\n bh=lisiD36YZIbkxbDmdw7YSlIrdor0JXtqx6qSb0Mj6NE=;\n b=wAaeE4wAYuAPwkaUqlhTwnKxEwutIKsacpk8tbHuSFEb+hggyMnidEUpCul7/ZsrQP\n FATuEnyCXPyih8MDUuTZGgKSYpgzmvIrmCP+MRHZOb4Dxc6uxnsHOWTU7iTBGaW9rgox\n raT96g+72SKCdZ+p9p9oSsM7TZIotYm5QT3pcQTRvhhOJz6QWTj12IFJTkghoEwnkhqP\n yxEvvHwSUfbV4YBuzMcpmOvilGnRgw5KGxWJjNpOvfZbOc7AFU/02MqyPSi8sU+oMNVz\n WyPpzT7p+EQNPafsGqxCobeRAR7koWhD3UaCkpSxH1RMSMg/mvGAc04ApXdSr9VqDqIu\n vsEA==",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20230601; t=1771002111; x=1771606911;\n h=content-transfer-encoding:mime-version:message-id:date:subject:cc\n :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date\n :message-id:reply-to;\n bh=lisiD36YZIbkxbDmdw7YSlIrdor0JXtqx6qSb0Mj6NE=;\n b=oQSZdvQQ18x0JRhccu88Ihow3dzuzarl5I5shyiGn0Wiqi3qySuwAYOnxaVtpfF8Vw\n GiMFjohIr+1XYiXibPyJfQ9bWm119Ydvi8spXL/NzKVYRnYi8FqBWUsj8K/aqUEvTz7Y\n lf2/9ylw5UOD6lv0zGL8My7WJZ6utjVIuRPXKTcLOlqdSKghxO0mYkUev8Hs/T/VrtRE\n 4d0Ub8MQg5mNXYlCFwLGgFCfyhJdPT4o8ks+Qx1+w1uSBefRRVPJkgYGKfEj0rLYKvm5\n i15stcJxY0x/Ve9MJ4Yl22sxGrYxhxLqfLTqRmYGVQDvVoDLZhS+Viu5x0Hifnen3yNo\n PmOQ==",
        "X-Gm-Message-State": "AOJu0YzZrQoYW5zlu/gS1smgFDtWZMYGuAQrXKf2xIulMfPp0XPm2l71\n y9H8CS3M+qaV3Nj5T+1Q73HODaQPhS85G6cHu6R+yAzMg13xll/oaMDW7lpIw7IvH+emoLz57Y/\n nEBB7cVE=",
        "X-Gm-Gg": "AZuq6aJsWJ3IKgfnmmNqtxYbc2x2HX03mc/VcjtzZFSvcz/UyYkwJ8FUNaapRPQw0iG\n OGkPyT62Er/fDx0okS02jRjmpE41HLyyFCD8L8+dNeXHgnUZ3pICK31IIaW0/UE8TRqoGLudmEX\n jAdLxjb5u6E2u81tB+R0DwPArYuVTnpq5wBM45aWgOfdAGf8kEg6LmA69bGHBd+irzFRNjDLG/L\n E6A/DJ+OPI17NUtpLSkmE+a0vvfbYxnjfxTR/Mm/hdtmw7GMK4oH6gIH1tt+DeCftyxSIDf+i0d\n EdbgWoCNU3yBR4I9WLRaQm+6u8SJHAavCxvuTn7qWepU4AuEFcCwaXBZEeKy/0OUzwQwBqUPc5M\n lVSPBM6DhnfaLITR8S+zQS5KWhXR28X6c9/wExw0qOknhu/VvgEMWYScotVGhNyXLmWNIGGMnOd\n t5XpgYAnY/Jhl+lC/j75soRvtU5H0pgpTRfQ==",
        "X-Received": "by 2002:a05:6122:8b0e:b0:566:f204:b49b with SMTP id\n 71dfb90a1353d-5676a8d8e1fmr730298e0c.2.1771002108519;\n Fri, 13 Feb 2026 09:01:48 -0800 (PST)",
        "From": "Adhemerval Zanella <adhemerval.zanella@linaro.org>",
        "To": "libc-alpha@sourceware.org",
        "Cc": "Siddhesh Poyarekar <siddhesh@gotplt.org>,\n Carlos O'Donell <carlos@redhat.com>",
        "Subject": "[PATCH v3] io: Refactor {n}ftw to use fts for stack safety and\n conformance (BZ 33882)",
        "Date": "Fri, 13 Feb 2026 14:00:43 -0300",
        "Message-ID": "<20260213170137.2145195-1-adhemerval.zanella@linaro.org>",
        "X-Mailer": "git-send-email 2.43.0",
        "MIME-Version": "1.0",
        "Content-Type": "text/plain; charset=UTF-8",
        "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": "The current implementation of nftw uses recursive function calls to\ntraverse the directory tree.  This approach is susceptible to stack\noverflow errors when traversing deeply nested directory structures,\nwhich can occur in legitimate workloads or be triggered by malicious\ninputs.\n\nThis patch reimplements nftw on top of fts, which uses an iterative\napproach with limited stack usage.  However, fts semantics differ\nslightly from glibc nftw requirements, which require some additional\nhandling:\n\n  * Physical Walk (FTW_PHYS) Symlink Handling, where fts checks symlink\n    targets even in physical mode (FTS_PHYS), returning FTS_SLNONE for\n    broken links.  This requires explicitly mapping both FTS_SL and\n    FTS_SLNONE to FTW_SL.  nftw (physical) must report the link itself,\n    regardless of the target's validity.\n\n  * Logical Walk (FTS_LOGICAL) and Cycles, where fts reports directory\n    cycles as FTS_DC.  These are mapped to FTW_SLN (Symbolic Link that\n    causes a cycle/cannot be followed), matching historical nftw behavior.\n\n  * Mount Point Crossings (FTW_MOUNT), where FTS_XDEV stops fts from\n    descending into mount points.  In FTW_DEPTH mode, nftw requires the\n    mount point directory itself to be reported, and since fts never\n    enters the directory, it never generates the FTS_DP (post-order)\n    event.  This is fixed by capturing the root device ID, and if the\n    directory is a mount point (different device ID), we treat the FTS_D\n    event as the required visit and report it as FTW_DP.\n\n  * Directory Changing (FTW_CHDIR), where FTS_LOGICAL implies FTS_NOCHDIR\n    in fts, preventing automatic directory changes.  This is implemented\n    by forcing FTS_NOCHDIR for all modes to maintain consistent fts\n    behavior and then manually managing FTW_CHDIR.\n\n  * Root Access Errors, where fts_read returns FTS_NS if the root path\n    cannot be accessed, while ftw expects a hard failure (-1) for\n    permission errors on the root.  This is fixed by an explicit check\n    for FTS_ROOTLEVEL.\n\n  * FTW_ACTIONRETVAL support, where it requires to map the FTW_SKIP_SUBTREE\n    and FTW_SKIP_SIBLINGS to specific logic paths.\n\nThis change unifies the traversal logic and eliminates the recursion\nlimit for file tree walks.\n\nAlso added tests for FTW_DNR, which is current missing.\n\nChecked on x86_64-linux-gnu and i686-linux-gnu.\n--\nChanges from v2:\n* Return -1 in case of memory allocation failure.\n\nChanges from v1:\n* Make tst-nftw-bz33882 as UNSUPPORTED if it can not create the deep\n  nested directory (some filesystem such as overlayfs does not support\n  this).\n---\n include/fts.h             |   50 ++\n io/Makefile               |    1 +\n io/fts.c                  |   41 +-\n io/fts64.c                |    1 +\n io/ftw.c                  | 1005 +++++++++++--------------------------\n io/ftw64-time64.c         |   10 +-\n io/ftw64.c                |   10 +-\n io/ftwtest-sh             |   13 +\n io/tst-nftw-bz33882.c     |  105 ++++\n sysdeps/wordsize-64/fts.c |   10 +-\n 10 files changed, 522 insertions(+), 724 deletions(-)\n create mode 100644 io/tst-nftw-bz33882.c",
    "diff": "diff --git a/include/fts.h b/include/fts.h\nindex ea36a9b9be..4a34d9357a 100644\n--- a/include/fts.h\n+++ b/include/fts.h\n@@ -47,6 +47,56 @@ typedef struct _ftsent64_time64\n } FSTENT64_TIME64;\n \n # endif\n+\n+__typeof (fts_open) __fts_open;\n+libc_hidden_proto (__fts_open);\n+__typeof (fts64_open) __fts64_open;\n+libc_hidden_proto (__fts64_open);\n+__typeof (fts64_open) __fts64_open;\n+libc_hidden_proto (__fts64_open);\n+#if __TIMESIZE != 64\n+extern FTS64_TIME64* __fts64_open_time64 (char *const*, int,\n+\t\t\t\t\t  int (*)(const FSTENT64_TIME64 **,\n+\t\t\t\t\t\t  const FSTENT64_TIME64 **));\n+libc_hidden_proto (__fts64_open_time64)\n+#endif\n+\n+__typeof (fts_close) __fts_close;\n+libc_hidden_proto (__fts_close);\n+__typeof (fts64_close) __fts64_close;\n+libc_hidden_proto (__fts64_close);\n+#if __TIMESIZE != 64\n+extern int __fts64_close_time64 (FTS64_TIME64 *);\n+libc_hidden_proto (__fts64_close_time64)\n+#endif\n+\n+__typeof (fts_read) __fts_read;\n+libc_hidden_proto (__fts_read);\n+__typeof (fts64_read) __fts64_read;\n+libc_hidden_proto (__fts64_read);\n+#if __TIMESIZE != 64\n+extern FSTENT64_TIME64* __fts64_read_time64 (FTS64_TIME64 *);\n+libc_hidden_proto (__fts64_read_time64)\n+#endif\n+\n+__typeof (fts_set) __fts_set;\n+libc_hidden_proto (__fts_set);\n+__typeof (fts64_set) __fts64_set;\n+libc_hidden_proto (__fts64_set);\n+#if __TIMESIZE != 64\n+extern int __fts64_set_time64 (FTS64_TIME64 *, FSTENT64_TIME64 *, int);\n+libc_hidden_proto (__fts64_set_time64)\n+#endif\n+\n+__typeof (fts_children) __fts_children;\n+libc_hidden_proto (__fts_children);\n+__typeof (fts64_children) __fts64_children;\n+libc_hidden_proto (__fts64_children);\n+#if __TIMESIZE != 64\n+extern FSTENT64_TIME64* __fts64_children_time64 (FTS64_TIME64 *, int);\n+libc_hidden_proto (__fts64_children_time64)\n+#endif\n+\n #endif\n \n #endif /* _FTS_H  */\ndiff --git a/io/Makefile b/io/Makefile\nindex 707161e10b..80e50578b2 100644\n--- a/io/Makefile\n+++ b/io/Makefile\n@@ -214,6 +214,7 @@ tests := \\\n   tst-mkdirat \\\n   tst-mkfifoat \\\n   tst-mknodat \\\n+  tst-nftw-bz33882 \\\n   tst-open-tmpfile \\\n   tst-openat \\\n   tst-posix_fallocate \\\ndiff --git a/io/fts.c b/io/fts.c\nindex 27a15b1104..3288d2e0fc 100644\n--- a/io/fts.c\n+++ b/io/fts.c\n@@ -74,11 +74,11 @@ static char sccsid[] = \"@(#)fts.c\t8.6 (Berkeley) 8/14/94\";\n \n /* Support for the LFS API version.  */\n #ifndef FTS_OPEN\n-#define FTS_OPEN fts_open\n-#define FTS_CLOSE fts_close\n-#define FTS_READ fts_read\n-#define FTS_SET fts_set\n-#define FTS_CHILDREN fts_children\n+# define FTS_OPEN fts_open\n+# define FTS_CLOSE fts_close\n+# define FTS_READ fts_read\n+# define FTS_SET fts_set\n+# define FTS_CHILDREN fts_children\n # define FTSOBJ FTS\n # define FTSENTRY FTSENT\n # define INO_T ino_t\n@@ -86,6 +86,20 @@ static char sccsid[] = \"@(#)fts.c\t8.6 (Berkeley) 8/14/94\";\n # define STAT __stat\n # define LSTAT __lstat\n # define FSTAT __fstat\n+# define FTS_INTERNAL_ALIAS\n+#endif\n+\n+#ifdef FTS_INTERNAL_ALIAS\n+# define _CONCAT(__x, __y)          __CONCAT (__x, __y)\n+# define FTS_INTERNAL_FUNC(__name)  _CONCAT (__, __name)\n+# define FTS_INTERNAL(__name)       FTS_INTERNAL_FUNC (__name)\n+# define FTS_HIDDEN_DEF(__name) \\\n+  weak_alias (FTS_INTERNAL_FUNC (__name), __name); \\\n+  libc_hidden_def (FTS_INTERNAL_FUNC (__name));\n+#else\n+# define FTS_INTERNAL(__name)     __name\n+# define FTS_HIDDEN_DEF(__name) \\\n+  libc_hidden_def (__name);\n #endif\n \n static FTSENTRY\t*fts_alloc (FTSOBJ *, const char *, size_t);\n@@ -119,8 +133,8 @@ static int      fts_safe_changedir (FTSOBJ *, FTSENTRY *, int, const char *);\n #define\tBREAD\t\t3\t\t/* fts_read */\n \n FTSOBJ *\n-FTS_OPEN (char * const *argv, int options,\n-\t  int (*compar) (const FTSENTRY **, const FTSENTRY **))\n+FTS_INTERNAL(FTS_OPEN) (char * const *argv, int options,\n+\t\t\tint (*compar) (const FTSENTRY **, const FTSENTRY **))\n {\n \tFTSOBJ *sp;\n \tFTSENTRY *p, *root;\n@@ -231,6 +245,7 @@ mem2:\tfree(sp->fts_path);\n mem1:\tfree(sp);\n \treturn (NULL);\n }\n+FTS_HIDDEN_DEF (FTS_OPEN);\n \n static void\n fts_load (FTSOBJ *sp, FTSENTRY *p)\n@@ -257,7 +272,7 @@ fts_load (FTSOBJ *sp, FTSENTRY *p)\n }\n \n int\n-FTS_CLOSE (FTSOBJ *sp)\n+FTS_INTERNAL (FTS_CLOSE) (FTSOBJ *sp)\n {\n \tFTSENTRY *freep, *p;\n \tint saved_errno;\n@@ -300,6 +315,7 @@ FTS_CLOSE (FTSOBJ *sp)\n \tfree(sp);\n \treturn (0);\n }\n+FTS_HIDDEN_DEF (FTS_CLOSE)\n \n /*\n  * Special case of \"/\" at the end of the path so that slashes aren't\n@@ -310,7 +326,7 @@ FTS_CLOSE (FTSOBJ *sp)\n \t    ? p->fts_pathlen - 1 : p->fts_pathlen)\n \n FTSENTRY *\n-FTS_READ (FTSOBJ *sp)\n+FTS_INTERNAL (FTS_READ) (FTSOBJ *sp)\n {\n \tFTSENTRY *p, *tmp;\n \tint instr;\n@@ -497,6 +513,7 @@ name:\t\tt = sp->fts_path + NAPPEND(p->fts_parent);\n \tp->fts_info = p->fts_errno ? FTS_ERR : FTS_DP;\n \treturn p;\n }\n+FTS_HIDDEN_DEF (FTS_READ)\n \n /*\n  * Fts_set takes the stream as an argument although it's not used in this\n@@ -506,7 +523,7 @@ name:\t\tt = sp->fts_path + NAPPEND(p->fts_parent);\n  */\n /* ARGSUSED */\n int\n-FTS_SET (FTSOBJ *sp, FTSENTRY *p, int instr)\n+FTS_INTERNAL (FTS_SET) (FTSOBJ *sp, FTSENTRY *p, int instr)\n {\n \tif (instr != 0 && instr != FTS_AGAIN && instr != FTS_FOLLOW &&\n \t    instr != FTS_NOINSTR && instr != FTS_SKIP) {\n@@ -516,9 +533,10 @@ FTS_SET (FTSOBJ *sp, FTSENTRY *p, int instr)\n \tp->fts_instr = instr;\n \treturn (0);\n }\n+FTS_HIDDEN_DEF (FTS_SET)\n \n FTSENTRY *\n-FTS_CHILDREN(FTSOBJ *sp, int instr)\n+FTS_INTERNAL(FTS_CHILDREN)(FTSOBJ *sp, int instr)\n {\n \tFTSENTRY *p;\n \tint fd;\n@@ -582,6 +600,7 @@ FTS_CHILDREN(FTSOBJ *sp, int instr)\n \t(void)__close(fd);\n \treturn (sp->fts_child);\n }\n+FTS_HIDDEN_DEF (FTS_CHILDREN)\n \n static inline int\n dirent_not_directory(const struct dirent *dp)\ndiff --git a/io/fts64.c b/io/fts64.c\nindex 152910018e..8cb433aa1a 100644\n--- a/io/fts64.c\n+++ b/io/fts64.c\n@@ -28,5 +28,6 @@\n #define STAT __stat64\n #define LSTAT __lstat64\n #define FSTAT __fstat64\n+#define FTS_INTERNAL_ALIAS\n \n #include \"fts.c\"\ndiff --git a/io/ftw.c b/io/ftw.c\nindex d29734813d..f3a5ba32f6 100644\n--- a/io/ftw.c\n+++ b/io/ftw.c\n@@ -16,116 +16,16 @@\n    License along with the GNU C Library; if not, see\n    <https://www.gnu.org/licenses/>.  */\n \n-#ifdef HAVE_CONFIG_H\n-# include <config.h>\n-#endif\n-\n-#if __GNUC__\n-# define alloca __builtin_alloca\n-#else\n-# if HAVE_ALLOCA_H\n-#  include <alloca.h>\n-# else\n-#  ifdef _AIX\n- #  pragma alloca\n-#  else\n-char *alloca ();\n-#  endif\n-# endif\n-#endif\n-\n-#ifdef _LIBC\n-# include <dirent.h>\n-# define NAMLEN(dirent) _D_EXACT_NAMLEN (dirent)\n-#else\n-# if HAVE_DIRENT_H\n-#  include <dirent.h>\n-#  define NAMLEN(dirent) strlen ((dirent)->d_name)\n-# else\n-#  define dirent direct\n-#  define NAMLEN(dirent) (dirent)->d_namlen\n-#  if HAVE_SYS_NDIR_H\n-#   include <sys/ndir.h>\n-#  endif\n-#  if HAVE_SYS_DIR_H\n-#   include <sys/dir.h>\n-#  endif\n-#  if HAVE_NDIR_H\n-#   include <ndir.h>\n-#  endif\n-# endif\n-#endif\n-\n #include <errno.h>\n #include <fcntl.h>\n+#include <fts.h>\n #include <ftw.h>\n-#include <limits.h>\n+#include <scratch_buffer.h>\n #include <search.h>\n+#include <stddef.h>\n #include <stdlib.h>\n #include <string.h>\n #include <unistd.h>\n-#include <not-cancel.h>\n-#include <sys/param.h>\n-#ifdef _LIBC\n-# include <include/sys/stat.h>\n-#else\n-# include <sys/stat.h>\n-#endif\n-\n-#if ! _LIBC && !HAVE_DECL_STPCPY && !defined stpcpy\n-char *stpcpy ();\n-#endif\n-\n-#if ! _LIBC && ! defined HAVE_MEMPCPY && ! defined mempcpy\n-/* Be CAREFUL that there are no side effects in N.  */\n-# define mempcpy(D, S, N) ((void *) ((char *) memcpy (D, S, N) + (N)))\n-#endif\n-\n-/* #define NDEBUG 1 */\n-#include <assert.h>\n-\n-#ifndef _LIBC\n-# undef __chdir\n-# define __chdir chdir\n-# undef __closedir\n-# define __closedir closedir\n-# undef __fchdir\n-# define __fchdir fchdir\n-# undef __getcwd\n-# define __getcwd(P, N) xgetcwd ()\n-extern char *xgetcwd (void);\n-# undef __mempcpy\n-# define __mempcpy mempcpy\n-# undef __opendir\n-# define __opendir opendir\n-# undef __readdir64\n-# define __readdir64 readdir\n-# undef __stpcpy\n-# define __stpcpy stpcpy\n-# undef __tdestroy\n-# define __tdestroy tdestroy\n-# undef __tfind\n-# define __tfind tfind\n-# undef __tsearch\n-# define __tsearch tsearch\n-# undef dirent64\n-# define dirent64 dirent\n-# undef MAX\n-# define MAX(a, b) ((a) > (b) ? (a) : (b))\n-#endif\n-\n-/* Arrange to make lstat calls go through the wrapper function\n-   on systems with an lstat function that does not dereference symlinks\n-   that are specified with a trailing slash.  */\n-#if ! _LIBC && ! LSTAT_FOLLOWS_SLASHED_SYMLINK\n-int rpl_lstat (const char *, struct stat *);\n-# undef lstat\n-# define lstat(Name, Stat_buf) rpl_lstat(Name, Stat_buf)\n-#endif\n-\n-#ifndef __set_errno\n-# define __set_errno(Val) errno = (Val)\n-#endif\n \n /* Support for the LFS API version.  */\n #ifndef FTW_NAME\n@@ -135,107 +35,24 @@ int rpl_lstat (const char *, struct stat *);\n # define NFTW_NEW_NAME __new_nftw\n # define INO_T ino_t\n # define STRUCT_STAT stat\n-# ifdef _LIBC\n-#  define LSTAT __lstat\n-#  define STAT __stat\n-#  define FSTATAT __fstatat\n-# else\n-#  define LSTAT lstat\n-#  define XTAT stat\n-#  define FSTATAT fstatat\n-# endif\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+#ifndef FTS_TYPE\n+# define FTS_TYPE    FTS\n+# define FTSENT_TYPE FTSENT\n+# define FTS_OPEN    __fts_open\n+# define FTS_READ    __fts_read\n+# define FTS_SET     __fts_set\n+# define FTS_CLOSE   __fts_close\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-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-  NFTW_FUNC_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-\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-/* Forward declarations of local functions.  */\n-static int ftw_dir (struct ftw_data *data, struct STRUCT_STAT *st,\n-\t\t    struct dir_data *old_dir);\n-\n-\n static int\n object_compare (const void *p1, const void *p2)\n {\n@@ -250,399 +67,37 @@ object_compare (const void *p1, const void *p2)\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+add_object (void **known_objects, 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+  return __tsearch (newp, 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+find_object (void **known_objects, 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+  return __tfind (&obj, known_objects, object_compare) != NULL;\n }\n \n-\n-static inline int\n-__attribute ((always_inline))\n-open_dir_stream (int *dfdp, struct ftw_data *data, struct dir_data *dirp)\n+union func_callback_t\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      int save_err = errno;\n-\t\t      free (buf);\n-\t\t      __set_errno (save_err);\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      int save_err = errno;\n-\t      free (buf);\n-\t      __set_errno (save_err);\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+  FTW_FUNC_T ftw_func;\n+  NFTW_FUNC_T nftw_func;\n+};\n \n static int\n-process_entry (struct ftw_data *data, struct dir_data *dir, const char *name,\n-\t       size_t namlen, int d_type)\n+ftw_startup (const char *dir, bool is_nftw, union func_callback_t func,\n+\t     int descriptors, int flags)\n {\n-  struct STRUCT_STAT st;\n-  int result = 0;\n-  int flag = 0;\n-  size_t new_buflen;\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    result = ftw_dir (data, &st, dir);\n-\t}\n-      else\n-\tresult = (*data->func) (data->dirbuf, &st, data->cvt_arr[flag],\n-\t\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-static int\n-__attribute ((noinline))\n-ftw_dir (struct ftw_data *data, struct STRUCT_STAT *st, struct dir_data *old_dir)\n-{\n-  struct dir_data dir;\n-  struct dirent64 *d;\n-  int previous_base = data->ftw.base;\n-  int result;\n-  char *startp;\n-\n-  /* Open the stream for this directory.  This might require that\n-     another stream has to be closed.  */\n-  result = open_dir_stream (old_dir == NULL ? NULL : &old_dir->streamfd,\n-\t\t\t    data, &dir);\n-  if (result != 0)\n-    {\n-      if (errno == EACCES)\n-\t/* We cannot read the directory.  Signal this with a special flag.  */\n-\tresult = (*data->func) (data->dirbuf, st, FTW_DNR, &data->ftw);\n-\n-      return result;\n-    }\n-\n-  /* First, report the directory (if not depth-first).  */\n-  if (!(data->flags & FTW_DEPTH))\n-    {\n-      result = (*data->func) (data->dirbuf, st, FTW_D, &data->ftw);\n-      if (result != 0)\n-\t{\n-\t  int save_err;\n-fail:\n-\t  save_err = errno;\n-\t  __closedir (dir.stream);\n-\t  dir.streamfd = -1;\n-\t  __set_errno (save_err);\n-\n-\t  if (data->actdir-- == 0)\n-\t    data->actdir = data->maxdir - 1;\n-\t  data->dirstreams[data->actdir] = NULL;\n-\t  return result;\n-\t}\n-    }\n-\n-  /* If necessary, change to this directory.  */\n-  if (data->flags & FTW_CHDIR)\n-    {\n-      if (__fchdir (__dirfd (dir.stream)) < 0)\n-\t{\n-\t  result = -1;\n-\t  goto fail;\n-\t}\n-    }\n-\n-  /* Next, update the `struct FTW' information.  */\n-  ++data->ftw.level;\n-  startp = strchr (data->dirbuf, '\\0');\n-  /* There always must be a directory name.  */\n-  assert (startp != data->dirbuf);\n-  if (startp[-1] != '/')\n-    *startp++ = '/';\n-  data->ftw.base = startp - data->dirbuf;\n-\n-  while (dir.stream != NULL && (d = __readdir64 (dir.stream)) != NULL)\n-    {\n-      int d_type = DT_UNKNOWN;\n-#ifdef _DIRENT_HAVE_D_TYPE\n-      d_type = d->d_type;\n-#endif\n-      result = process_entry (data, &dir, d->d_name, NAMLEN (d), d_type);\n-      if (result != 0)\n-\tbreak;\n-    }\n-\n-  if (dir.stream != NULL)\n-    {\n-      /* The stream is still open.  I.e., we did not need more\n-\t descriptors.  Simply close the stream now.  */\n-      int save_err = errno;\n-\n-      assert (dir.content == NULL);\n-\n-      __closedir (dir.stream);\n-      dir.streamfd = -1;\n-      __set_errno (save_err);\n-\n-      if (data->actdir-- == 0)\n-\tdata->actdir = data->maxdir - 1;\n-      data->dirstreams[data->actdir] = NULL;\n-    }\n-  else\n-    {\n-      int save_err;\n-      char *runp = dir.content;\n-\n-      while (result == 0 && *runp != '\\0')\n-\t{\n-\t  char *endp = strchr (runp, '\\0');\n-\n-\t  // XXX Should store the d_type values as well?!\n-\t  result = process_entry (data, &dir, runp, endp - runp, DT_UNKNOWN);\n-\n-\t  runp = endp + 1;\n-\t}\n-\n-      save_err = errno;\n-      free (dir.content);\n-      __set_errno (save_err);\n-    }\n-\n-  if ((data->flags & FTW_ACTIONRETVAL) && result == FTW_SKIP_SIBLINGS)\n-    result = 0;\n-\n-  /* Prepare the return, revert the `struct FTW' information.  */\n-  data->dirbuf[data->ftw.base - 1] = '\\0';\n-  --data->ftw.level;\n-  data->ftw.base = previous_base;\n-\n-  /* Finally, if we process depth-first report the directory.  */\n-  if (result == 0 && (data->flags & FTW_DEPTH))\n-    result = (*data->func) (data->dirbuf, st, FTW_DP, &data->ftw);\n-\n-  if (old_dir\n-      && (data->flags & FTW_CHDIR)\n-      && (result == 0\n-\t  || ((data->flags & FTW_ACTIONRETVAL)\n-\t      && (result != -1 && result != FTW_STOP))))\n-    {\n-      /* Change back to the parent directory.  */\n-      int done = 0;\n-      if (old_dir->stream != NULL)\n-\tif (__fchdir (__dirfd (old_dir->stream)) == 0)\n-\t  done = 1;\n-\n-      if (!done)\n-\t{\n-\t  if (data->ftw.base == 1)\n-\t    {\n-\t      if (__chdir (\"/\") < 0)\n-\t\tresult = -1;\n-\t    }\n-\t  else\n-\t    if (__chdir (\"..\") < 0)\n-\t      result = -1;\n-\t}\n-    }\n-\n-  return result;\n-}\n-\n-\n-static int\n-__attribute ((noinline))\n-ftw_startup (const char *dir, int is_nftw, void *func, int descriptors,\n-\t     int flags)\n-{\n-  struct ftw_data data = { .dirstreams = NULL };\n-  struct STRUCT_STAT st;\n-  int result = 0;\n-  int save_err;\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@@ -650,181 +105,319 @@ ftw_startup (const char *dir, int is_nftw, void *func, int descriptors,\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+  /* Data structure for keeping fingerprints of already processed\n+     object.  This is needed when not using FTW_PHYS.  */\n+  void *known_objects = NULL;\n+  /* Device of starting point.  Needed for FTW_MOUNT.  */\n+  dev_t root_dev = 0;\n+\n+  /* NB: The fts FTS_LOGICAL implies on FTS_NOCHDIR, so to to proper implement\n+     FTW_CHDIR it requires manually manage the chdir / fchdir dance around\n+     the user's callback.  The BUF is used to create the required path.  */\n+  struct scratch_buffer buf;\n+  scratch_buffer_init (&buf);\n+\n+  int start_fd = __open (\".\", O_RDONLY | O_CLOEXEC);\n+  if (start_fd < -1)\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+  int fts_options = 0;\n+  if (flags & FTW_PHYS)\n+    fts_options |= FTS_PHYSICAL;\n+  else\n+    fts_options |= FTS_LOGICAL;\n \n-  /* Find basename.  */\n-  while (cp > data.dirbuf && cp[-1] != '/')\n-    --cp;\n-  data.ftw.base = cp - data.dirbuf;\n+  if (flags & FTW_MOUNT)\n+    fts_options |= FTS_XDEV;\n \n-  data.flags = flags;\n+  if (!(flags & FTW_CHDIR))\n+    fts_options |= FTS_NOCHDIR;\n \n-  /* This assignment might seem to be strange but it is what we want.\n-     The trick is that the first three arguments to the `ftw' and\n-     `nftw' callback functions are equal.  Therefore we can call in\n-     every case the callback using the format of the `nftw' version\n-     and get the correct result since the stack layout for a function\n-     call in C allows this.  */\n-  data.func = (NFTW_FUNC_T) func;\n+  char *const paths[] = { (char *)dir, NULL };\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+  /* NB: ingnore 'descriptors' limits since fts manages fds dynamically.  */\n+  FTS_TYPE *ftsp = FTS_OPEN (paths, fts_options, NULL);\n+  if (!ftsp)\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+      __close (start_fd);\n+      return -1;\n     }\n \n-  /* Get stat info for start directory.  */\n-  if (result == 0)\n+  FTSENT_TYPE *ent = NULL;\n+  int rc = 0;\n+  int save_err;\n+\n+  bool postorder = (flags & FTW_DEPTH) != 0;\n+\n+  /* Used to proper support FTW_SKIP_SIBLINGS to avoid call fts_read again\n+     of the next iteration.  */\n+  bool skip_read = false;\n+\n+  while (true)\n     {\n-      const char *name;\n+      if (!skip_read)\n+\t{\n+\t  errno = 0;\n+          ent = FTS_READ (ftsp);\n+        }\n+      skip_read = false;\n \n-      if (data.flags & FTW_CHDIR)\n+      if (ent == NULL)\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+\t  if (errno != 0)\n+\t    goto done;\n+\t  break;\n+        }\n \n-      if (((flags & FTW_PHYS)\n-\t   ? LSTAT (name, &st)\n-\t   : STAT (name, &st)) < 0)\n+      if (ent->fts_level == FTS_ROOTLEVEL)\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 = (*data.func) (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  /* If the STARTING path cannot be accessed, nftw must fail rather\n+\t     than calling the callback with FTW_NS (it is required only for\n+\t     FTS_ROOTLEVEL).  */\n+\t  if (ent->fts_info == FTS_NS && ent->fts_errno != 0)\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+\t      rc = -1;\n+\t      errno = ent->fts_errno;\n+\t      goto done;\n+            }\n \n-\t      /* We know this directory now.  */\n-\t      if (!(flags & FTW_PHYS))\n-\t\tresult = add_object (&data, &st);\n+\t  /* Remember the device of the initial directory in case FTW_MOUNT. */\n+\t  if (ent->fts_statp)\n+\t    root_dev = ent->fts_statp->st_dev;\n+        }\n \n-\t      if (result == 0)\n-\t\tresult = ftw_dir (&data, &st, NULL);\n+      /* Handle FTW_MOUNT.  */\n+      bool is_mount_crossing = false;\n+      if ((flags & FTW_MOUNT) && ent->fts_level > FTS_ROOTLEVEL\n+\t  && ent->fts_statp)\n+\tif (ent->fts_statp->st_dev != root_dev)\n+\t  is_mount_crossing = true;\n+\n+      int fn_flag;\n+      switch (ent->fts_info)\n+\t{\n+\tcase FTS_D:\n+\t  /* Remember the device of the initial directory in case FTW_MOUNT\n+\t     is given.  */\n+\t  if (ent->fts_level == FTS_ROOTLEVEL)\n+\t    {\n+\t      if (!(flags & FTW_PHYS)\n+\t\t  && add_object (&known_objects, ent->fts_statp) == -1)\n+\t\t{\n+\t\t  rc = -1;\n+\t\t  goto done;\n+\t\t}\n \t    }\n \t  else\n \t    {\n-\t      int flag = S_ISLNK (st.st_mode) ? FTW_SL : FTW_F;\n-\n-\t      result = (*data.func) (data.dirbuf, &st, data.cvt_arr[flag],\n-\t\t\t\t     &data.ftw);\n+\t      if (!(flags & FTW_PHYS)\n+\t\t  && find_object (&known_objects, ent->fts_statp))\n+\t\tcontinue;\n+\t      if (add_object (&known_objects, ent->fts_statp) == -1)\n+\t\t{\n+\t\t  rc = -1;\n+\t\t  goto done;\n+\t\t}\n \t    }\n-\t}\n \n-      if ((flags & FTW_ACTIONRETVAL)\n-\t  && (result == FTW_SKIP_SUBTREE || result == FTW_SKIP_SIBLINGS))\n-\tresult = 0;\n+\t  /* Normally we skip FTS_D in depth mode, but for a mount\n+\t     crossing, fts (FTS_XDEV) will NOT descend.  We will never\n+\t     get FTS_DP.  We must handle it manually.  */\n+\t  if (postorder && !is_mount_crossing)\n+\t    continue;\n+\n+\t  /* fts returns FTS_D for directories it has not tried to open yet,\n+\t     even if it has not read permissions.  The nftw must report\n+\t     FTW_DNR instead.   */\n+\t  if (__faccessat (start_fd, ent->fts_accpath, R_OK, AT_EACCESS) == 0)\n+\t    fn_flag = FTW_D;\n+\t  else\n+\t    {\n+\t      fn_flag = FTW_DNR;\n+\t      FTS_SET (ftsp, ent, FTS_SKIP);\n+\t    }\n+\t  break;\n+\n+\tcase FTS_DP:\n+\t  if (!postorder)\n+\t    continue;\n+\t  fn_flag = FTW_DP;\n+\t  break;\n+\n+\tcase FTS_DEFAULT:\n+        case FTS_F:\n+\t  fn_flag = FTW_F;\n+\t  break;\n+\n+        case FTS_SL:\n+\t  fn_flag = FTW_SL;\n+\t  break;\n+\n+\tcase FTS_SLNONE:\n+\t  fn_flag = FTW_SLN;\n+\t  break;\n+\n+\tcase FTS_DNR:\n+\t  fn_flag = FTW_DNR;\n+\t  break;\n+\n+\tcase FTS_NS:\n+\t  fn_flag = FTW_NS;\n+\t  break;\n+\n+        case FTS_DC:\n+\t  if (S_ISDIR (ent->fts_statp->st_mode))\n+\t    {\n+\t      if (!(flags & FTW_PHYS)\n+\t\t  && find_object (&known_objects, ent->fts_statp))\n+\t\tcontinue;\n+\t      if (add_object (&known_objects, ent->fts_statp) == -1)\n+\t\t{\n+\t\t  rc = 1;\n+\t\t  goto done;\n+\t\t}\n+\t    }\n+\t  /* A directory cycle was detected (Logical walk only) and\n+\t     instead of aborting with ELOOP, we report this as a\n+\t     symlink that cannot be successfully followed (FTW_SLN).  */\n+          fn_flag = FTW_SLN;\n+          break;\n+\n+        default:\n+\t  rc = -1;\n+\t  goto done;\n+        }\n+\n+        struct FTW ftw_data = {\n+\t  .base = ent->fts_pathlen - ent->fts_namelen,\n+\t  .level = ent->fts_level\n+\t};\n+\n+        bool chdir_performed = false;\n+\tif (flags & FTW_CHDIR)\n+\t  {\n+\t    const char *target_dir = NULL;\n+\n+\t    size_t dir_len = ent->fts_pathlen - ent->fts_namelen;\n+\t    if (dir_len == 0)\n+\t      /* Case: \"filename\" (Root level file/dir with no path prefix):\n+\t\t the containing directory is the start directory.  */\n+              target_dir = \".\";\n+            else\n+\t      {\n+\t\t/* Case: \"path/to/filename\": we need to extract \"path/to\"  */\n+\t\tif (!scratch_buffer_set_array_size (&buf, dir_len, 1))\n+\t\t  goto done;\n+\t\tmemcpy (buf.data, ent->fts_accpath, dir_len);\n+\t\tchar *target = buf.data;\n+\t\tif (dir_len > 1 && target[dir_len-1] == '/')\n+\t\t  target[dir_len-1] = '\\0';\n+\t\ttarget_dir = target;\n+\t      }\n+\n+            if (__chdir (target_dir) == 0)\n+              chdir_performed = true;\n+            else\n+\t      {\n+\t\trc = -1;\n+\t\tgoto done;\n+\t      }\n+\t  }\n+\n+        rc = is_nftw\n+\t  ? func.nftw_func (ent->fts_path, ent->fts_statp, fn_flag, &ftw_data)\n+\t  : func.ftw_func (ent->fts_path, ent->fts_statp, fn_flag);\n+\n+\tif (chdir_performed && __fchdir (start_fd) != 0)\n+\t  {\n+\t    rc = -1;\n+\t    goto done;\n+\t  }\n+\n+\tif (!(flags & FTW_ACTIONRETVAL))\n+\t  {\n+\t    if (rc != 0)\n+\t      break;\n+\t    continue;\n+\t  }\n+\t/* FTW_ACTIONRETVAL support.  */\n+\tswitch (rc)\n+\t  {\n+\t  case FTW_CONTINUE:\n+\t    /* Default behavior: just proceed to next entry.  */\n+\t    break;\n+\n+\t  case FTW_STOP:\n+\t    /* Stop traversal immediately (success return code).  We set rc=0\n+\t       here because FTW_STOP is considered a \"successful\" stop,\n+\t       unlike a non-zero return in standard mode.  */\n+\t    rc = 0;\n+\t    goto done;\n+\n+\t  case FTW_SKIP_SUBTREE:\n+\t    /* Only meaningful if we are currently visiting a directory in\n+\t       pre-order (FTS_D).  */\n+\t    if (ent->fts_info == FTS_D)\n+\t      FTS_SET (ftsp, ent, FTS_SKIP);\n+\t    break;\n+\n+\t  case FTW_SKIP_SIBLINGS:\n+\t    /* We must skip everything until we emerge at a lower level\n+\t       (parent).  */\n+\t    {\n+\t      int current_level = ent->fts_level;\n+\n+\t      /* Drain fts until level < current.  */\n+\t      while ((ent = FTS_READ (ftsp)) != NULL)\n+\t\t{\n+\t\t  if (ent->fts_level < current_level)\n+\t\t    {\n+\t\t      skip_read = true;\n+\t\t      break;\n+\t\t    }\n+\t\t}\n+\n+\t\tif (ent == NULL)\n+\t\t  goto done;\n+\t    } break;\n+\n+\t  default:\n+\t    rc = 0;\n+\t    goto done;\n+\t  }\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+done:\n+  scratch_buffer_free (&buf);\n+  __tdestroy (known_objects, free);\n   save_err = errno;\n-  __tdestroy (data.known_objects, free);\n-  free (data.dirstreams);\n+  FTS_CLOSE (ftsp);\n+  __close (start_fd);\n   __set_errno (save_err);\n \n-  return result;\n+  return rc;\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, 0, func, descriptors, 0);\n+  return ftw_startup (path,\n+\t\t      false,\n+\t\t      (union func_callback_t) { .ftw_func = func },\n+\t\t      descriptors,\n+\t\t      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, 1, func, descriptors, flags);\n+  return ftw_startup (path, true,\n+\t\t      (union func_callback_t) { .nftw_func = func },\n+\t\t      descriptors,\n+\t\t      flags);\n }\n #else\n \n@@ -841,7 +434,11 @@ NFTW_NEW_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags)\n       __set_errno (EINVAL);\n       return -1;\n     }\n-  return ftw_startup (path, 1, func, descriptors, flags);\n+  return ftw_startup (path,\n+\t\t      true,\n+\t\t      (union func_callback_t) { .nftw_func = func },\n+\t\t      descriptors,\n+\t\t      flags);\n }\n versioned_symbol (libc, NFTW_NEW_NAME, NFTW_NAME, GLIBC_2_3_3);\n \n@@ -856,7 +453,11 @@ 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, 1, func, descriptors, flags);\n+  return ftw_startup (path,\n+\t\t      true,\n+\t\t      (union func_callback_t) { .nftw_func = func },\n+\t\t      descriptors,\n+\t\t      flags);\n }\n \n compat_symbol (libc, NFTW_OLD_NAME, NFTW_NAME, GLIBC_2_1);\ndiff --git a/io/ftw64-time64.c b/io/ftw64-time64.c\nindex 2df871f802..517dc93d92 100644\n--- a/io/ftw64-time64.c\n+++ b/io/ftw64-time64.c\n@@ -23,11 +23,15 @@\n # define NFTW_NAME      __nftw64_time64\n # define INO_T          ino64_t\n # define STRUCT_STAT    __stat64_t64\n-# define LSTAT          __lstat64_time64\n-# define STAT           __stat64_time64\n-# define FSTATAT        __fstatat64_time64\n # define FTW_FUNC_T     __ftw64_time64_func_t\n # define NFTW_FUNC_T    __nftw64_time64_func_t\n \n+# define FTS_TYPE       FTS64_TIME64\n+# define FTSENT_TYPE    FSTENT64_TIME64\n+# define FTS_OPEN       __fts64_open_time64\n+# define FTS_READ       __fts64_read_time64\n+# define FTS_SET        __fts64_set_time64\n+# define FTS_CLOSE      __fts64_close_time64\n+\n # include \"ftw.c\"\n #endif\ndiff --git a/io/ftw64.c b/io/ftw64.c\nindex 0d7cb30091..b61b0e24b8 100644\n--- a/io/ftw64.c\n+++ b/io/ftw64.c\n@@ -22,10 +22,14 @@\n #define NFTW_NEW_NAME __new_nftw64\n #define INO_T ino64_t\n #define STRUCT_STAT stat64\n-#define LSTAT __lstat64\n-#define STAT __stat64\n-#define FSTATAT __fstatat64\n #define FTW_FUNC_T __ftw64_func_t\n #define NFTW_FUNC_T __nftw64_func_t\n \n+#define FTS_TYPE FTS64\n+#define FTSENT_TYPE FTSENT64\n+#define FTS_OPEN __fts64_open\n+#define FTS_READ __fts64_read\n+#define FTS_SET __fts64_set\n+#define FTS_CLOSE __fts64_close\n+\n #include \"ftw.c\"\ndiff --git a/io/ftwtest-sh b/io/ftwtest-sh\nindex 9758e18f0d..a8e5160210 100644\n--- a/io/ftwtest-sh\n+++ b/io/ftwtest-sh\n@@ -62,6 +62,8 @@ ln -s $tmpdir/foo/lvl1/lvl2 $tmpdir/foo/lvl1/lvl2/link@2\n ln -s $tmpdir/foo/lvl1/lvl2/lvl3/lvl4 $tmpdir/foo/lvl1/link@1\n echo > $tmpdir/bar/xo\n chmod a-x,a+r $tmpdir/bar\n+mkdir $tmpdir/ndir\n+chmod a-r $tmpdir/ndir\n \n testout=$(mktemp $tmp/ftwtest-tmp-XXXXXX.out)\n \n@@ -73,6 +75,7 @@ base = \"$tmp/\", file = \"$ftwtest\", flag = FTW_D, level = 0\n base = \"$tmp/$ftwtest/\", file = \"bar\", flag = FTW_D, level = 1\n base = \"$tmp/$ftwtest/\", file = \"baz\", flag = FTW_F, level = 1\n base = \"$tmp/$ftwtest/\", file = \"foo\", flag = FTW_D, level = 1\n+base = \"$tmp/$ftwtest/\", file = \"ndir\", flag = FTW_DNR, level = 1\n base = \"$tmp/$ftwtest/bar/\", file = \"xo\", flag = FTW_NS, level = 2\n base = \"$tmp/$ftwtest/foo/\", file = \"lvl1\", flag = FTW_D, level = 2\n base = \"$tmp/$ftwtest/foo/lvl1/\", file = \"file@1\", flag = FTW_F, level = 3\n@@ -92,6 +95,7 @@ base = \"$tmp/\", file = \"$ftwtest\", flag = FTW_DP, level = 0\n base = \"$tmp/$ftwtest/\", file = \"bar\", flag = FTW_DP, level = 1\n base = \"$tmp/$ftwtest/\", file = \"baz\", flag = FTW_F, level = 1\n base = \"$tmp/$ftwtest/\", file = \"foo\", flag = FTW_DP, level = 1\n+base = \"$tmp/$ftwtest/\", file = \"ndir\", flag = FTW_DNR, level = 1\n base = \"$tmp/$ftwtest/bar/\", file = \"xo\", flag = FTW_NS, level = 2\n base = \"$tmp/$ftwtest/foo/\", file = \"lvl1\", flag = FTW_DP, level = 2\n base = \"$tmp/$ftwtest/foo/lvl1/\", file = \"file@1\", flag = FTW_F, level = 3\n@@ -111,6 +115,7 @@ base = \"$tmp/\", file = \"$ftwtest\", flag = FTW_D, level = 0\n base = \"$tmp/$ftwtest/\", file = \"bar\", flag = FTW_D, level = 1\n base = \"$tmp/$ftwtest/\", file = \"baz\", flag = FTW_F, level = 1\n base = \"$tmp/$ftwtest/\", file = \"foo\", flag = FTW_D, level = 1\n+base = \"$tmp/$ftwtest/\", file = \"ndir\", flag = FTW_DNR, level = 1\n base = \"$tmp/$ftwtest/bar/\", file = \"xo\", flag = FTW_NS, level = 2\n base = \"$tmp/$ftwtest/foo/\", file = \"lvl1\", flag = FTW_D, level = 2\n base = \"$tmp/$ftwtest/foo/lvl1/\", file = \"file@1\", flag = FTW_F, level = 3\n@@ -125,7 +130,9 @@ EOF\n rm $testout\n \n # For the next test everything must be readable.\n+chmod a+r $tmpdir/ndir\n chmod -fR a+x $tmpdir\n+chmod a-r $tmpdir/ndir\n \n $testprogram --chdir $tmpdir |\n     sort > $testout\n@@ -138,6 +145,7 @@ base = \"$tmp/\", file = \"$ftwtest\", flag = FTW_D, cwd = $tmpreal, level = 0\n base = \"$tmp/$ftwtest/\", file = \"bar\", flag = FTW_D, cwd = $tmpreal/$ftwtest, level = 1\n base = \"$tmp/$ftwtest/\", file = \"baz\", flag = FTW_F, cwd = $tmpreal/$ftwtest, level = 1\n base = \"$tmp/$ftwtest/\", file = \"foo\", flag = FTW_D, cwd = $tmpreal/$ftwtest, level = 1\n+base = \"$tmp/$ftwtest/\", file = \"ndir\", flag = FTW_DNR, cwd = $tmpreal/$ftwtest, level = 1\n base = \"$tmp/$ftwtest/bar/\", file = \"xo\", flag = FTW_F, cwd = $tmpreal/$ftwtest/bar, level = 2\n base = \"$tmp/$ftwtest/foo/\", file = \"lvl1\", flag = FTW_D, cwd = $tmpreal/$ftwtest/foo, level = 2\n base = \"$tmp/$ftwtest/foo/lvl1/\", file = \"file@1\", flag = FTW_F, cwd = $tmpreal/$ftwtest/foo/lvl1, level = 3\n@@ -160,6 +168,7 @@ base = \"\", file = \"$ftwtest\", flag = FTW_D, cwd = $tmpreal, level = 0\n base = \"$ftwtest/\", file = \"bar\", flag = FTW_D, cwd = $tmpreal/$ftwtest, level = 1\n base = \"$ftwtest/\", file = \"baz\", flag = FTW_F, cwd = $tmpreal/$ftwtest, level = 1\n base = \"$ftwtest/\", file = \"foo\", flag = FTW_D, cwd = $tmpreal/$ftwtest, level = 1\n+base = \"$ftwtest/\", file = \"ndir\", flag = FTW_DNR, cwd = $tmpreal/$ftwtest, level = 1\n base = \"$ftwtest/bar/\", file = \"xo\", flag = FTW_F, cwd = $tmpreal/$ftwtest/bar, level = 2\n base = \"$ftwtest/foo/\", file = \"lvl1\", flag = FTW_D, cwd = $tmpreal/$ftwtest/foo, level = 2\n base = \"$ftwtest/foo/lvl1/\", file = \"file@1\", flag = FTW_F, cwd = $tmpreal/$ftwtest/foo/lvl1, level = 3\n@@ -182,6 +191,7 @@ base = \"$ftwtest/\", file = \".\", flag = FTW_D, cwd = $tmpreal/$ftwtest, level = 0\n base = \"$ftwtest/./\", file = \"bar\", flag = FTW_D, cwd = $tmpreal/$ftwtest, level = 1\n base = \"$ftwtest/./\", file = \"baz\", flag = FTW_F, cwd = $tmpreal/$ftwtest, level = 1\n base = \"$ftwtest/./\", file = \"foo\", flag = FTW_D, cwd = $tmpreal/$ftwtest, level = 1\n+base = \"$ftwtest/./\", file = \"ndir\", flag = FTW_DNR, cwd = $tmpreal/$ftwtest, level = 1\n base = \"$ftwtest/./bar/\", file = \"xo\", flag = FTW_F, cwd = $tmpreal/$ftwtest/bar, level = 2\n base = \"$ftwtest/./foo/\", file = \"lvl1\", flag = FTW_D, cwd = $tmpreal/$ftwtest/foo, level = 2\n base = \"$ftwtest/./foo/lvl1/\", file = \"file@1\", flag = FTW_F, cwd = $tmpreal/$ftwtest/foo/lvl1, level = 3\n@@ -226,6 +236,7 @@ base = \"$tmp/\", file = \"$ftwtest\", flag = FTW_D, level = 0\n base = \"$tmp/$ftwtest/\", file = \"bar\", flag = FTW_D, level = 1\n base = \"$tmp/$ftwtest/\", file = \"baz\", flag = FTW_F, level = 1\n base = \"$tmp/$ftwtest/\", file = \"foo\", flag = FTW_D, level = 1\n+base = \"$tmp/$ftwtest/\", file = \"ndir\", flag = FTW_DNR, level = 1\n base = \"$tmp/$ftwtest/bar/\", file = \"xo\", flag = FTW_F, level = 2\n base = \"$tmp/$ftwtest/foo/\", file = \"lvl1\", flag = FTW_D, level = 2\n base = \"$tmp/$ftwtest/foo/\", file = \"lvl1b\", flag = FTW_D, level = 2\n@@ -250,6 +261,7 @@ base = \"$tmp/\", file = \"$ftwtest\", flag = FTW_D, level = 0\n base = \"$tmp/$ftwtest/\", file = \"bar\", flag = FTW_D, level = 1\n base = \"$tmp/$ftwtest/\", file = \"baz\", flag = FTW_F, level = 1\n base = \"$tmp/$ftwtest/\", file = \"foo\", flag = FTW_D, level = 1\n+base = \"$tmp/$ftwtest/\", file = \"ndir\", flag = FTW_DNR, level = 1\n base = \"$tmp/$ftwtest/bar/\", file = \"xo\", flag = FTW_F, level = 2\n base = \"$tmp/$ftwtest/foo/\", file = \"lvl1\", flag = FTW_D, level = 2\n base = \"$tmp/$ftwtest/foo/\", file = \"lvl1b\", flag = FTW_D, level = 2\n@@ -275,6 +287,7 @@ base = \"$tmp/\", file = \"$ftwtest\", flag = FTW_D, level = 0\n base = \"$tmp/$ftwtest/\", file = \"bar\", flag = FTW_D, level = 1\n base = \"$tmp/$ftwtest/\", file = \"baz\", flag = FTW_F, level = 1\n base = \"$tmp/$ftwtest/\", file = \"foo\", flag = FTW_D, level = 1\n+base = \"$tmp/$ftwtest/\", file = \"ndir\", flag = FTW_DNR, level = 1\n base = \"$tmp/$ftwtest/bar/\", file = \"xo\", flag = FTW_F, level = 2\n base = \"$tmp/$ftwtest/foo/\", file = \"lvl1\", flag = FTW_D, level = 2\n base = \"$tmp/$ftwtest/foo/\", file = \"lvl1b\", flag = FTW_D, level = 2\ndiff --git a/io/tst-nftw-bz33882.c b/io/tst-nftw-bz33882.c\nnew file mode 100644\nindex 0000000000..5764558ff7\n--- /dev/null\n+++ b/io/tst-nftw-bz33882.c\n@@ -0,0 +1,105 @@\n+/* Check if nested directory level does not overflow the stack (BZ #33882)\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+   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+#include <errno.h>\n+#include <ftw.h>\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <support/check.h>\n+#include <support/support.h>\n+#include <support/temp_file.h>\n+#include <support/xunistd.h>\n+#include <sys/resource.h>\n+\n+/* Typical stack frame for a recursive function is 64–256 bytes, with a nested\n+   depth of 5000 would required around 640Kb of stack space.  */\n+enum { nested_depth = 5000 };\n+enum { stack_limit_kb = 512 };\n+\n+/* Short name to maximize depth/path ratio.  */\n+static const char dir_name[] = \"d\";\n+\n+static void\n+do_cleanup (void)\n+{\n+  xchdir (\"..\");\n+  for (int i = 0; i < nested_depth; i++)\n+    {\n+      remove (dir_name);\n+      xchdir (\"..\");\n+    }\n+  remove (dir_name);\n+}\n+#define CLEANUP_HANDLER do_cleanup\n+\n+static void\n+check_mkdir (const char *path)\n+{\n+  int r = mkdir (path, 0700);\n+  /* Some filesystem such as overlayfs does not support larger path required\n+     to trigger the internal buffer reallocation.  */\n+  if (r != 0)\n+    {\n+      if (errno == ENAMETOOLONG)\n+\tFAIL_UNSUPPORTED (\"the filesystem does not support the required\"\n+\t\t\t  \"large path\");\n+      else\n+\tFAIL_EXIT1 (\"mkdir (\\\"%s\\\", 0%o): %m\", path, 0700);\n+    }\n+}\n+\n+static int\n+my_func (const char *file, const struct stat *sb, int flag, struct FTW *ftwbuf)\n+{\n+  return 0;\n+}\n+\n+/* Set the RLIMIT_AS limit to the value in *LIMIT.  */\n+static void\n+xsetrlimit_stack (const struct rlimit *limit)\n+{\n+  if (setrlimit (RLIMIT_STACK, limit) != 0)\n+    FAIL_EXIT1 (\"setrlimit (RLIMIT_STACK, %lu): %m\",\n+                (unsigned long) limit->rlim_cur);\n+}\n+\n+static int\n+do_test (void)\n+{\n+  xsetrlimit_stack (&(struct rlimit) { .rlim_cur = stack_limit_kb * 1024,\n+\t\t\t\t       .rlim_max = stack_limit_kb * 1024 });\n+\n+  char *tempdir = support_create_temp_directory (\"tst-bz33882\");\n+\n+  xchdir (tempdir);\n+  for (int i = 0; i < nested_depth; i++)\n+    {\n+      check_mkdir (dir_name);\n+      xchdir (dir_name);\n+    }\n+\n+  TEST_COMPARE (nftw (tempdir, my_func, 20, 0), 0);\n+\n+  free (tempdir);\n+\n+  do_cleanup ();\n+\n+  return 0;\n+}\n+\n+#include <support/test-driver.c>\ndiff --git a/sysdeps/wordsize-64/fts.c b/sysdeps/wordsize-64/fts.c\nindex 159dc1febe..b10d81714a 100644\n--- a/sysdeps/wordsize-64/fts.c\n+++ b/sysdeps/wordsize-64/fts.c\n@@ -12,8 +12,8 @@\n #undef fts64_set\n #undef fts64_children\n \n-weak_alias (fts_open, fts64_open)\n-weak_alias (fts_close, fts64_close)\n-weak_alias (fts_read, fts64_read)\n-weak_alias (fts_set, fts64_set)\n-weak_alias (fts_children, fts64_children)\n+weak_alias (__fts_open, fts64_open)\n+weak_alias (__fts_close, fts64_close)\n+weak_alias (__fts_read, fts64_read)\n+weak_alias (__fts_set, fts64_set)\n+weak_alias (__fts_children, fts64_children)\n",
    "prefixes": [
        "v3"
    ]
}