[PATCH] io: Refactor {n}ftw to use fts for stack safety and conformance (BZ 33882)
Adhemerval Zanella Netto
adhemerval.zanella@linaro.org
Fri Feb 13 15:38:10 GMT 2026
On 13/02/26 11:43, Carlos O'Donell wrote:
> On 2/12/26 2:31 PM, Adhemerval Zanella wrote:
>> The current implementation of nftw uses recursive function calls to
>> traverse the directory tree. This approach is susceptible to stack
>> overflow errors when traversing deeply nested directory structures,
>> which can occur in legitimate workloads or be triggered by malicious
>> inputs.
>
> Great improvement!
>
> I would note that this is no different than regular expressions or
> other workloads that have the ability to consume arbitrary memory
> in a way that is not easily computable. You do have to take care that
> in scanning a directory you cause the state machine of nftw to consume
> all the availble resources. Even after your changes below we still
> have to call malloc to add objects to the known set of objects and that
> can also run out of memory. At least you reliably now fail if we can't
> allocate space for the known object, but I think we miss reporting this
> correctly. See my note below about rc being set to -1.
Oops, I just sent a v2 before seeing your review to address the CI failure.
Let me check if I need to send a v3:
>
> I assume fixing it to consume a limited amount of stack has consequences
> on the performance? Do we know what these consequences are?
It seems to be slower, running a simple testcase:
$ cat /tmp/t.c
#include <ftw.h>
static int
my_func (const char *file, const struct stat *sb, int flag, struct FTW *ftwbuf)
{
return 0;
}
int main (int argc, char *argv[])
{
nftw (argv[1], my_func, 32, 0);
}
Is is indeed slower:
x86_64-linux-gnu-master $ time elf/ld.so --library-path . /tmp/t /usr
real 0m0,707s
user 0m0,057s
sys 0m0,650s
x86_64-linux-gnu $ time elf/ld.so --library-path . /tmp/t /usr
real 0m1,412s
user 0m0,127s
sys 0m1,283s
And memory usage seems somewhat similar. For the previous example,
using 'valgrind --tool=massif --stacks=yes' master shows:
MB
4.219^ #
| #:::
| @:::#:::
| @::@@:::#:::
| @ :@@@::@@:::#:::
| :@::::@@@::@@:::#:::
| :::::@::::@@@::@@:::#:::
| ::: :::@::::@@@::@@:::#::::
| @::::::: :::@::::@@@::@@:::#::::
| :::::@::::::: :::@::::@@@::@@:::#::::
| @:::: ::@::::::: :::@::::@@@::@@:::#::::
| @::::@:::: ::@::::::: :::@::::@@@::@@:::#::::
| :::@::: @:::: ::@::::::: :::@::::@@@::@@:::#::::
| :::::: :@::: @:::: ::@::::::: :::@::::@@@::@@:::#::::
| :::: ::: :@::: @:::: ::@::::::: :::@::::@@@::@@:::#::::
| ::::::: ::: :@::: @:::: ::@::::::: :::@::::@@@::@@:::#::::
| @@ ::::: ::::: ::: :@::: @:::: ::@::::::: :::@::::@@@::@@:::#::::
| @@ :::: : ::::: ::: :@::: @:::: ::@::::::: :::@::::@@@::@@:::#::::
| @@:@@ :::: : ::::: ::: :@::: @:::: ::@::::::: :::@::::@@@::@@:::#::::
| @@@ :@@ :::: : ::::: ::: :@::: @:::: ::@::::::: :::@::::@@@::@@:::#::::
0 +----------------------------------------------------------------------->Mi
0 269.3
With the maximum stack size of 6528.
While the new implementation shows:
MB
5.649^ #
| #
| #
| @ #
| @ #
| @ @ #
| @ @ # :::
| @ @ : # :::::@
| @ @ : :: ::::#::::::@
| @ :: @ : : ::@::::#::::::@
| @ : @ : :: :@::::@::::#::::::@
| :: @ : @ : ::::::@: ::@::::#::::::@
| : @ : @:: : :@:::: :@: ::@::::#::::::@
| : @ :: ::@: ::::::::@:::: :@: ::@::::#::::::@
| @ : : @:::::::: ::@: :::: :::@:::: :@: ::@::::#::::::@
| @ : :: :: ::::@:: ::::: ::@: :::: :::@:::: :@: ::@::::#::::::@
| @ @@ ::::::::: ::::@:: ::::: ::@: :::: :::@:::: :@: ::@::::#::::::@
| @ @ ::: :::::: ::::@:: ::::: ::@: :::: :::@:::: :@: ::@::::#::::::@
| @ @@ ::: :::::: ::::@:: ::::: ::@: :::: :::@:::: :@: ::@::::#::::::@
| @:::@@ ::: :::::: ::::@:: ::::: ::@: :::: :::@:::: :@: ::@::::#::::::@
0 +----------------------------------------------------------------------->Mi
0 668.6
With the maximum stack size of 2304.
However it does way better memory usage for the pathological cases. Using the
nested 5000 directories from tst-nftw-bz33882 massif for master shows:
MB
8.040^ #
| @@@@#
| @@@@@@@@#
| :::::@@@@@@@@#
| ::@::: :: @@@@@@@@#
| @::: @::: :: @@@@@@@@#
| @:::@::: @::: :: @@@@@@@@#
| ::::@:: @::: @::: :: @@@@@@@@#
| :@:::::: @:: @::: @::: :: @@@@@@@@#:
| @@@::@: :::: @:: @::: @::: :: @@@@@@@@#:
| :@@@@ ::@: :::: @:: @::: @::: :: @@@@@@@@#:
| ::::::@@@@ ::@: :::: @:: @::: @::: :: @@@@@@@@#:
| :@@: ::::@@@@ ::@: :::: @:: @::: @::: :: @@@@@@@@#:
| :@:::@@: ::::@@@@ ::@: :::: @:: @::: @::: :: @@@@@@@@#:
| :@@:@: :@@: ::::@@@@ ::@: :::: @:: @::: @::: :: @@@@@@@@#:
| ::@@:@ :@: :@@: ::::@@@@ ::@: :::: @:: @::: @::: :: @@@@@@@@#:
| @@:: @@:@ :@: :@@: ::::@@@@ ::@: :::: @:: @::: @::: :: @@@@@@@@#:
| ::@@:: @@:@ :@: :@@: ::::@@@@ ::@: :::: @:: @::: @::: :: @@@@@@@@#:
| @@::@@:: @@:@ :@: :@@: ::::@@@@ ::@: :::: @:: @::: @::: :: @@@@@@@@#:
| @ ::@@:: @@:@ :@: :@@: ::::@@@@ ::@: :::: @:: @::: @::: :: @@@@@@@@#:
0 +----------------------------------------------------------------------->Mi
0 16.42
[...]
--------------------------------------------------------------------------------
n time(i) total(B) useful-heap(B) extra-heap(B) stacks(B)
--------------------------------------------------------------------------------
59 15,508,767 8,082,520 1,261,974 4,991,186 1,829,360
60 15,554,800 8,100,368 1,262,482 5,003,854 1,834,032
While with this new approach:
KB
754.9^ :
| :@@@@#:
| @@@@@@:@@@@#:
| @@@@@@@@@@@:@@@@#:
| @@@@@@@@@@@@@@@:@@@@#:
| @@:::::@@@@@@@@@@@@@@@:@@@@#:
| :@@@@ : :: @@@@@@@@@@@@@@@:@@@@#:
| :::::@@ @ : :: @@@@@@@@@@@@@@@:@@@@#:
| @@@@::::::@@ @ : :: @@@@@@@@@@@@@@@:@@@@#:
| @@@@@@::::::@@ @ : :: @@@@@@@@@@@@@@@:@@@@#:
| @@@@::@@@@@@::::::@@ @ : :: @@@@@@@@@@@@@@@:@@@@#:
| @::@@@@::@@@@@@::::::@@ @ : :: @@@@@@@@@@@@@@@:@@@@#::
| ::@: @@@@::@@@@@@::::::@@ @ : :: @@@@@@@@@@@@@@@:@@@@#::
| :@:::@: @@@@::@@@@@@::::::@@ @ : :: @@@@@@@@@@@@@@@:@@@@#::
| @@::@:::@: @@@@::@@@@@@::::::@@ @ : :: @@@@@@@@@@@@@@@:@@@@#::
| @:@@::@:::@: @@@@::@@@@@@::::::@@ @ : :: @@@@@@@@@@@@@@@:@@@@#::
| @ :@:@@::@:::@: @@@@::@@@@@@::::::@@ @ : :: @@@@@@@@@@@@@@@:@@@@#::
| @@@::@:@@::@:::@: @@@@::@@@@@@::::::@@ @ : :: @@@@@@@@@@@@@@@:@@@@#::
| @@@::@:@@::@:::@: @@@@::@@@@@@::::::@@ @ : :: @@@@@@@@@@@@@@@:@@@@#::
| ::@@@::@:@@::@:::@: @@@@::@@@@@@::::::@@ @ : :: @@@@@@@@@@@@@@@:@@@@#::
0 +----------------------------------------------------------------------->Mi
0 15.76
[...]
--------------------------------------------------------------------------------
n time(i) total(B) useful-heap(B) extra-heap(B) stacks(B)
--------------------------------------------------------------------------------
58 12,241,266 659,936 588,844 69,292 1,800
59 12,325,811 662,832 591,124 69,596 2,112
And I do not if we can implement a feasible {n}ftw with recursion *without*
imposing some path limits (which is an arbitrary limits that conflict with the
GNU Coding Standards).
Maybe we can optimize it better by reimplement a FTS without some extra
requirement, but this will also duplicate code.
>
>> This patch reimplements nftw on top of fts, which uses an iterative
>> approach with limited stack usage. However, fts semantics differ
>> slightly from glibc nftw requirements, which require some additional
>> handling:
>>
>> * Physical Walk (FTW_PHYS) Symlink Handling, where fts checks symlink
>> targets even in physical mode (FTS_PHYS), returning FTS_SLNONE for
>> broken links. This requires explicitly mapping both FTS_SL and
>> FTS_SLNONE to FTW_SL. nftw (physical) must report the link itself,
>> regardless of the target's validity.
>>
>> * Logical Walk (FTS_LOGICAL) and Cycles, where fts reports directory
>> cycles as FTS_DC. These are mapped to FTW_SLN (Symbolic Link that
>> causes a cycle/cannot be followed), matching historical nftw behavior.
>>
>> * Mount Point Crossings (FTW_MOUNT), where FTS_XDEV stops fts from
>> descending into mount points. In FTW_DEPTH mode, nftw requires the
>> mount point directory itself to be reported, and since fts never
>> enters the directory, it never generates the FTS_DP (post-order)
>> event. This is fixed by capturing the root device ID, and if the
>> directory is a mount point (different device ID), we treat the FTS_D
>> event as the required visit and report it as FTW_DP.
>>
>> * Directory Changing (FTW_CHDIR), where FTS_LOGICAL implies FTS_NOCHDIR
>> in fts, preventing automatic directory changes. This is implemented
>> by forcing FTS_NOCHDIR for all modes to maintain consistent fts
>> behavior and then manually managing FTW_CHDIR.
>>
>> * Root Access Errors, where fts_read returns FTS_NS if the root path
>> cannot be accessed, while ftw expects a hard failure (-1) for
>> permission errors on the root. This is fixed by an explicit check
>> for FTS_ROOTLEVEL.
>>
>> * FTW_ACTIONRETVAL support, where it requires to map the FTW_SKIP_SUBTREE
>> and FTW_SKIP_SIBLINGS to specific logic paths.
>>
>> This change unifies the traversal logic and eliminates the recursion
>> limit for file tree walks.
>>
>> Also added tests for FTW_DNR, which is current missing.
>>
>> Checked on x86_64-linux-gnu and i686-linux-gnu. I also checked with the
>> LTP nftw tests.
>> ---
>> include/fts.h | 50 ++
>> io/Makefile | 1 +
>> io/fts.c | 41 +-
>> io/fts64.c | 1 +
>> io/ftw.c | 996 +++++++++++---------------------------
>> io/ftw64-time64.c | 10 +-
>> io/ftw64.c | 10 +-
>> io/ftwtest-sh | 13 +
>> io/tst-nftw-bz33882.c | 88 ++++
>> sysdeps/wordsize-64/fts.c | 10 +-
>> 10 files changed, 496 insertions(+), 724 deletions(-)
>> create mode 100644 io/tst-nftw-bz33882.c
>>
>> diff --git a/include/fts.h b/include/fts.h
>> index ea36a9b9be..4a34d9357a 100644
>> --- a/include/fts.h
>> +++ b/include/fts.h
>> @@ -47,6 +47,56 @@ typedef struct _ftsent64_time64
>> } FSTENT64_TIME64;
>> # endif
>> +
>> +__typeof (fts_open) __fts_open;
>> +libc_hidden_proto (__fts_open);
>> +__typeof (fts64_open) __fts64_open;
>> +libc_hidden_proto (__fts64_open);
>> +__typeof (fts64_open) __fts64_open;
>> +libc_hidden_proto (__fts64_open);
>> +#if __TIMESIZE != 64
>> +extern FTS64_TIME64* __fts64_open_time64 (char *const*, int,
>> + int (*)(const FSTENT64_TIME64 **,
>> + const FSTENT64_TIME64 **));
>> +libc_hidden_proto (__fts64_open_time64)
>> +#endif
>> +
>> +__typeof (fts_close) __fts_close;
>> +libc_hidden_proto (__fts_close);
>> +__typeof (fts64_close) __fts64_close;
>> +libc_hidden_proto (__fts64_close);
>> +#if __TIMESIZE != 64
>> +extern int __fts64_close_time64 (FTS64_TIME64 *);
>> +libc_hidden_proto (__fts64_close_time64)
>> +#endif
>> +
>> +__typeof (fts_read) __fts_read;
>> +libc_hidden_proto (__fts_read);
>> +__typeof (fts64_read) __fts64_read;
>> +libc_hidden_proto (__fts64_read);
>> +#if __TIMESIZE != 64
>> +extern FSTENT64_TIME64* __fts64_read_time64 (FTS64_TIME64 *);
>> +libc_hidden_proto (__fts64_read_time64)
>> +#endif
>> +
>> +__typeof (fts_set) __fts_set;
>> +libc_hidden_proto (__fts_set);
>> +__typeof (fts64_set) __fts64_set;
>> +libc_hidden_proto (__fts64_set);
>> +#if __TIMESIZE != 64
>> +extern int __fts64_set_time64 (FTS64_TIME64 *, FSTENT64_TIME64 *, int);
>> +libc_hidden_proto (__fts64_set_time64)
>> +#endif
>> +
>> +__typeof (fts_children) __fts_children;
>> +libc_hidden_proto (__fts_children);
>> +__typeof (fts64_children) __fts64_children;
>> +libc_hidden_proto (__fts64_children);
>> +#if __TIMESIZE != 64
>> +extern FSTENT64_TIME64* __fts64_children_time64 (FTS64_TIME64 *, int);
>> +libc_hidden_proto (__fts64_children_time64)
>> +#endif
>> +
>> #endif
>> #endif /* _FTS_H */
>> diff --git a/io/Makefile b/io/Makefile
>> index 707161e10b..80e50578b2 100644
>> --- a/io/Makefile
>> +++ b/io/Makefile
>> @@ -214,6 +214,7 @@ tests := \
>> tst-mkdirat \
>> tst-mkfifoat \
>> tst-mknodat \
>> + tst-nftw-bz33882 \
>> tst-open-tmpfile \
>> tst-openat \
>> tst-posix_fallocate \
>> diff --git a/io/fts.c b/io/fts.c
>> index 27a15b1104..3288d2e0fc 100644
>> --- a/io/fts.c
>> +++ b/io/fts.c
>> @@ -74,11 +74,11 @@ static char sccsid[] = "@(#)fts.c 8.6 (Berkeley) 8/14/94";
>> /* Support for the LFS API version. */
>> #ifndef FTS_OPEN
>> -#define FTS_OPEN fts_open
>> -#define FTS_CLOSE fts_close
>> -#define FTS_READ fts_read
>> -#define FTS_SET fts_set
>> -#define FTS_CHILDREN fts_children
>> +# define FTS_OPEN fts_open
>> +# define FTS_CLOSE fts_close
>> +# define FTS_READ fts_read
>> +# define FTS_SET fts_set
>> +# define FTS_CHILDREN fts_children
>> # define FTSOBJ FTS
>> # define FTSENTRY FTSENT
>> # define INO_T ino_t
>> @@ -86,6 +86,20 @@ static char sccsid[] = "@(#)fts.c 8.6 (Berkeley) 8/14/94";
>> # define STAT __stat
>> # define LSTAT __lstat
>> # define FSTAT __fstat
>> +# define FTS_INTERNAL_ALIAS
>> +#endif
>> +
>> +#ifdef FTS_INTERNAL_ALIAS
>> +# define _CONCAT(__x, __y) __CONCAT (__x, __y)
>> +# define FTS_INTERNAL_FUNC(__name) _CONCAT (__, __name)
>> +# define FTS_INTERNAL(__name) FTS_INTERNAL_FUNC (__name)
>> +# define FTS_HIDDEN_DEF(__name) \
>> + weak_alias (FTS_INTERNAL_FUNC (__name), __name); \
>> + libc_hidden_def (FTS_INTERNAL_FUNC (__name));
>> +#else
>> +# define FTS_INTERNAL(__name) __name
>> +# define FTS_HIDDEN_DEF(__name) \
>> + libc_hidden_def (__name);
>> #endif
>> static FTSENTRY *fts_alloc (FTSOBJ *, const char *, size_t);
>> @@ -119,8 +133,8 @@ static int fts_safe_changedir (FTSOBJ *, FTSENTRY *, int, const char *);
>> #define BREAD 3 /* fts_read */
>> FTSOBJ *
>> -FTS_OPEN (char * const *argv, int options,
>> - int (*compar) (const FTSENTRY **, const FTSENTRY **))
>> +FTS_INTERNAL(FTS_OPEN) (char * const *argv, int options,
>> + int (*compar) (const FTSENTRY **, const FTSENTRY **))
>> {
>> FTSOBJ *sp;
>> FTSENTRY *p, *root;
>> @@ -231,6 +245,7 @@ mem2: free(sp->fts_path);
>> mem1: free(sp);
>> return (NULL);
>> }
>> +FTS_HIDDEN_DEF (FTS_OPEN);
>> static void
>> fts_load (FTSOBJ *sp, FTSENTRY *p)
>> @@ -257,7 +272,7 @@ fts_load (FTSOBJ *sp, FTSENTRY *p)
>> }
>> int
>> -FTS_CLOSE (FTSOBJ *sp)
>> +FTS_INTERNAL (FTS_CLOSE) (FTSOBJ *sp)
>> {
>> FTSENTRY *freep, *p;
>> int saved_errno;
>> @@ -300,6 +315,7 @@ FTS_CLOSE (FTSOBJ *sp)
>> free(sp);
>> return (0);
>> }
>> +FTS_HIDDEN_DEF (FTS_CLOSE)
>> /*
>> * Special case of "/" at the end of the path so that slashes aren't
>> @@ -310,7 +326,7 @@ FTS_CLOSE (FTSOBJ *sp)
>> ? p->fts_pathlen - 1 : p->fts_pathlen)
>> FTSENTRY *
>> -FTS_READ (FTSOBJ *sp)
>> +FTS_INTERNAL (FTS_READ) (FTSOBJ *sp)
>> {
>> FTSENTRY *p, *tmp;
>> int instr;
>> @@ -497,6 +513,7 @@ name: t = sp->fts_path + NAPPEND(p->fts_parent);
>> p->fts_info = p->fts_errno ? FTS_ERR : FTS_DP;
>> return p;
>> }
>> +FTS_HIDDEN_DEF (FTS_READ)
>> /*
>> * Fts_set takes the stream as an argument although it's not used in this
>> @@ -506,7 +523,7 @@ name: t = sp->fts_path + NAPPEND(p->fts_parent);
>> */
>> /* ARGSUSED */
>> int
>> -FTS_SET (FTSOBJ *sp, FTSENTRY *p, int instr)
>> +FTS_INTERNAL (FTS_SET) (FTSOBJ *sp, FTSENTRY *p, int instr)
>> {
>> if (instr != 0 && instr != FTS_AGAIN && instr != FTS_FOLLOW &&
>> instr != FTS_NOINSTR && instr != FTS_SKIP) {
>> @@ -516,9 +533,10 @@ FTS_SET (FTSOBJ *sp, FTSENTRY *p, int instr)
>> p->fts_instr = instr;
>> return (0);
>> }
>> +FTS_HIDDEN_DEF (FTS_SET)
>> FTSENTRY *
>> -FTS_CHILDREN(FTSOBJ *sp, int instr)
>> +FTS_INTERNAL(FTS_CHILDREN)(FTSOBJ *sp, int instr)
>> {
>> FTSENTRY *p;
>> int fd;
>> @@ -582,6 +600,7 @@ FTS_CHILDREN(FTSOBJ *sp, int instr)
>> (void)__close(fd);
>> return (sp->fts_child);
>> }
>> +FTS_HIDDEN_DEF (FTS_CHILDREN)
>> static inline int
>> dirent_not_directory(const struct dirent *dp)
>> diff --git a/io/fts64.c b/io/fts64.c
>> index 152910018e..8cb433aa1a 100644
>> --- a/io/fts64.c
>> +++ b/io/fts64.c
>> @@ -28,5 +28,6 @@
>> #define STAT __stat64
>> #define LSTAT __lstat64
>> #define FSTAT __fstat64
>> +#define FTS_INTERNAL_ALIAS
>> #include "fts.c"
>> diff --git a/io/ftw.c b/io/ftw.c
>> index d29734813d..14161d2ae3 100644
>> --- a/io/ftw.c
>> +++ b/io/ftw.c
>> @@ -16,116 +16,16 @@
>> License along with the GNU C Library; if not, see
>> <https://www.gnu.org/licenses/>. */
>> -#ifdef HAVE_CONFIG_H
>> -# include <config.h>
>> -#endif
>> -
>> -#if __GNUC__
>> -# define alloca __builtin_alloca
>> -#else
>> -# if HAVE_ALLOCA_H
>> -# include <alloca.h>
>> -# else
>> -# ifdef _AIX
>> - # pragma alloca
>> -# else
>> -char *alloca ();
>> -# endif
>> -# endif
>> -#endif
>> -
>> -#ifdef _LIBC
>> -# include <dirent.h>
>> -# define NAMLEN(dirent) _D_EXACT_NAMLEN (dirent)
>> -#else
>> -# if HAVE_DIRENT_H
>> -# include <dirent.h>
>> -# define NAMLEN(dirent) strlen ((dirent)->d_name)
>> -# else
>> -# define dirent direct
>> -# define NAMLEN(dirent) (dirent)->d_namlen
>> -# if HAVE_SYS_NDIR_H
>> -# include <sys/ndir.h>
>> -# endif
>> -# if HAVE_SYS_DIR_H
>> -# include <sys/dir.h>
>> -# endif
>> -# if HAVE_NDIR_H
>> -# include <ndir.h>
>> -# endif
>> -# endif
>> -#endif
>> -
>> #include <errno.h>
>> #include <fcntl.h>
>> +#include <fts.h>
>> #include <ftw.h>
>> -#include <limits.h>
>> +#include <scratch_buffer.h>
>> #include <search.h>
>> +#include <stddef.h>
>> #include <stdlib.h>
>> #include <string.h>
>> #include <unistd.h>
>> -#include <not-cancel.h>
>> -#include <sys/param.h>
>> -#ifdef _LIBC
>> -# include <include/sys/stat.h>
>> -#else
>> -# include <sys/stat.h>
>> -#endif
>> -
>> -#if ! _LIBC && !HAVE_DECL_STPCPY && !defined stpcpy
>> -char *stpcpy ();
>> -#endif
>> -
>> -#if ! _LIBC && ! defined HAVE_MEMPCPY && ! defined mempcpy
>> -/* Be CAREFUL that there are no side effects in N. */
>> -# define mempcpy(D, S, N) ((void *) ((char *) memcpy (D, S, N) + (N)))
>> -#endif
>> -
>> -/* #define NDEBUG 1 */
>> -#include <assert.h>
>> -
>> -#ifndef _LIBC
>> -# undef __chdir
>> -# define __chdir chdir
>> -# undef __closedir
>> -# define __closedir closedir
>> -# undef __fchdir
>> -# define __fchdir fchdir
>> -# undef __getcwd
>> -# define __getcwd(P, N) xgetcwd ()
>> -extern char *xgetcwd (void);
>> -# undef __mempcpy
>> -# define __mempcpy mempcpy
>> -# undef __opendir
>> -# define __opendir opendir
>> -# undef __readdir64
>> -# define __readdir64 readdir
>> -# undef __stpcpy
>> -# define __stpcpy stpcpy
>> -# undef __tdestroy
>> -# define __tdestroy tdestroy
>> -# undef __tfind
>> -# define __tfind tfind
>> -# undef __tsearch
>> -# define __tsearch tsearch
>> -# undef dirent64
>> -# define dirent64 dirent
>> -# undef MAX
>> -# define MAX(a, b) ((a) > (b) ? (a) : (b))
>> -#endif
>> -
>> -/* Arrange to make lstat calls go through the wrapper function
>> - on systems with an lstat function that does not dereference symlinks
>> - that are specified with a trailing slash. */
>> -#if ! _LIBC && ! LSTAT_FOLLOWS_SLASHED_SYMLINK
>> -int rpl_lstat (const char *, struct stat *);
>> -# undef lstat
>> -# define lstat(Name, Stat_buf) rpl_lstat(Name, Stat_buf)
>> -#endif
>> -
>> -#ifndef __set_errno
>> -# define __set_errno(Val) errno = (Val)
>> -#endif
>> /* Support for the LFS API version. */
>> #ifndef FTW_NAME
>> @@ -135,107 +35,24 @@ int rpl_lstat (const char *, struct stat *);
>> # define NFTW_NEW_NAME __new_nftw
>> # define INO_T ino_t
>> # define STRUCT_STAT stat
>> -# ifdef _LIBC
>> -# define LSTAT __lstat
>> -# define STAT __stat
>> -# define FSTATAT __fstatat
>> -# else
>> -# define LSTAT lstat
>> -# define XTAT stat
>> -# define FSTATAT fstatat
>> -# endif
>> # define FTW_FUNC_T __ftw_func_t
>> # define NFTW_FUNC_T __nftw_func_t
>> #endif
>> -
>> -/* We define PATH_MAX if the system does not provide a definition.
>> - This does not artificially limit any operation. PATH_MAX is simply
>> - used as a guesstimate for the expected maximal path length.
>> - Buffers will be enlarged if necessary. */
>> -#ifndef PATH_MAX
>> -# define PATH_MAX 1024
>> +#ifndef FTS_TYPE
>> +# define FTS_TYPE FTS
>> +# define FTSENT_TYPE FTSENT
>> +# define FTS_OPEN __fts_open
>> +# define FTS_READ __fts_read
>> +# define FTS_SET __fts_set
>> +# define FTS_CLOSE __fts_close
>> #endif
>> -struct dir_data
>> -{
>> - DIR *stream;
>> - int streamfd;
>> - char *content;
>> -};
>> -
>> struct known_object
>> {
>> dev_t dev;
>> INO_T ino;
>> };
>> -struct ftw_data
>> -{
>> - /* Array with pointers to open directory streams. */
>> - struct dir_data **dirstreams;
>> - size_t actdir;
>> - size_t maxdir;
>> -
>> - /* Buffer containing name of currently processed object. */
>> - char *dirbuf;
>> - size_t dirbufsize;
>> -
>> - /* Passed as fourth argument to `nftw' callback. The `base' member
>> - tracks the content of the `dirbuf'. */
>> - struct FTW ftw;
>> -
>> - /* Flags passed to `nftw' function. 0 for `ftw'. */
>> - int flags;
>> -
>> - /* Conversion array for flag values. It is the identity mapping for
>> - `nftw' calls, otherwise it maps the values to those known by
>> - `ftw'. */
>> - const int *cvt_arr;
>> -
>> - /* Callback function. We always use the `nftw' form. */
>> - NFTW_FUNC_T func;
>> -
>> - /* Device of starting point. Needed for FTW_MOUNT. */
>> - dev_t dev;
>> -
>> - /* Data structure for keeping fingerprints of already processed
>> - object. This is needed when not using FTW_PHYS. */
>> - void *known_objects;
>> -};
>> -
>> -static bool
>> -ftw_allocate (struct ftw_data *data, size_t newsize)
>> -{
>> - void *newp = realloc (data->dirstreams, data->maxdir
>> - * sizeof (struct dir_data *)
>> - + newsize);
>> - if (newp == NULL)
>> - return false;
>> - data->dirstreams = newp;
>> - data->dirbufsize = newsize;
>> - data->dirbuf = (char *) data->dirstreams
>> - + data->maxdir * sizeof (struct dir_data *);
>> - return true;
>> -}
>> -
>> -/* Internally we use the FTW_* constants used for `nftw'. When invoked
>> - as `ftw', map each flag to the subset of values used by `ftw'. */
>> -static const int nftw_arr[] =
>> -{
>> - FTW_F, FTW_D, FTW_DNR, FTW_NS, FTW_SL, FTW_DP, FTW_SLN
>> -};
>> -
>> -static const int ftw_arr[] =
>> -{
>> - FTW_F, FTW_D, FTW_DNR, FTW_NS, FTW_F, FTW_D, FTW_NS
>> -};
>> -
>> -
>> -/* Forward declarations of local functions. */
>> -static int ftw_dir (struct ftw_data *data, struct STRUCT_STAT *st,
>> - struct dir_data *old_dir);
>> -
>> -
>> static int
>> object_compare (const void *p1, const void *p2)
>> {
>> @@ -250,399 +67,37 @@ object_compare (const void *p1, const void *p2)
>> return (kp1->dev > kp2->dev) - (kp1->dev < kp2->dev);
>> }
>> -
>> static int
>> -add_object (struct ftw_data *data, struct STRUCT_STAT *st)
>> +add_object (void **known_objects, struct STRUCT_STAT *st)
>> {
>> struct known_object *newp = malloc (sizeof (struct known_object));
>> if (newp == NULL)
>> return -1;
>
> OK. We aleays return -1.
>
>> newp->dev = st->st_dev;
>> newp->ino = st->st_ino;
>> - return __tsearch (newp, &data->known_objects, object_compare) ? 0 : -1;
>> + return __tsearch (newp, known_objects, object_compare) ? 0 : -1;
>> }
>> static inline int
>> -find_object (struct ftw_data *data, struct STRUCT_STAT *st)
>> +find_object (void **known_objects, struct STRUCT_STAT *st)
>> {
>> struct known_object obj;
>> obj.dev = st->st_dev;
>> obj.ino = st->st_ino;
>> - return __tfind (&obj, &data->known_objects, object_compare) != NULL;
>> + return __tfind (&obj, known_objects, object_compare) != NULL;
>> }
>> -
>> -static inline int
>> -__attribute ((always_inline))
>> -open_dir_stream (int *dfdp, struct ftw_data *data, struct dir_data *dirp)
>> +union func_callback_t
>> {
>> - int result = 0;
>> -
>> - if (data->dirstreams[data->actdir] != NULL)
>> - {
>> - /* Oh, oh. We must close this stream. Get all remaining
>> - entries and store them as a list in the `content' member of
>> - the `struct dir_data' variable. */
>> - size_t bufsize = 1024;
>> - char *buf = malloc (bufsize);
>> -
>> - if (buf == NULL)
>> - result = -1;
>> - else
>> - {
>> - DIR *st = data->dirstreams[data->actdir]->stream;
>> - struct dirent64 *d;
>> - size_t actsize = 0;
>> -
>> - while ((d = __readdir64 (st)) != NULL)
>> - {
>> - size_t this_len = NAMLEN (d);
>> - if (actsize + this_len + 2 >= bufsize)
>> - {
>> - char *newp;
>> - bufsize += MAX (1024, 2 * this_len);
>> - newp = (char *) realloc (buf, bufsize);
>> - if (newp == NULL)
>> - {
>> - /* No more memory. */
>> - int save_err = errno;
>> - free (buf);
>> - __set_errno (save_err);
>> - return -1;
>> - }
>> - buf = newp;
>> - }
>> -
>> - *((char *) __mempcpy (buf + actsize, d->d_name, this_len))
>> - = '\0';
>> - actsize += this_len + 1;
>> - }
>> -
>> - /* Terminate the list with an additional NUL byte. */
>> - buf[actsize++] = '\0';
>> -
>> - /* Shrink the buffer to what we actually need. */
>> - void *content = realloc (buf, actsize);
>> - data->dirstreams[data->actdir]->content = content;
>> - if (content == NULL)
>> - {
>> - int save_err = errno;
>> - free (buf);
>> - __set_errno (save_err);
>> - result = -1;
>> - }
>> - else
>> - {
>> - __closedir (st);
>> - data->dirstreams[data->actdir]->stream = NULL;
>> - data->dirstreams[data->actdir]->streamfd = -1;
>> - data->dirstreams[data->actdir] = NULL;
>> - }
>> - }
>> - }
>> -
>> - /* Open the new stream. */
>> - if (result == 0)
>> - {
>> - assert (data->dirstreams[data->actdir] == NULL);
>> -
>> - if (dfdp != NULL && *dfdp != -1)
>> - {
>> - int fd = __openat64_nocancel (*dfdp, data->dirbuf + data->ftw.base,
>> - O_RDONLY | O_DIRECTORY | O_NDELAY);
>> - dirp->stream = NULL;
>> - if (fd != -1 && (dirp->stream = __fdopendir (fd)) == NULL)
>> - __close_nocancel_nostatus (fd);
>> - }
>> - else
>> - {
>> - const char *name;
>> -
>> - if (data->flags & FTW_CHDIR)
>> - {
>> - name = data->dirbuf + data->ftw.base;
>> - if (name[0] == '\0')
>> - name = ".";
>> - }
>> - else
>> - name = data->dirbuf;
>> -
>> - dirp->stream = __opendir (name);
>> - }
>> -
>> - if (dirp->stream == NULL)
>> - result = -1;
>> - else
>> - {
>> - dirp->streamfd = __dirfd (dirp->stream);
>> - dirp->content = NULL;
>> - data->dirstreams[data->actdir] = dirp;
>> -
>> - if (++data->actdir == data->maxdir)
>> - data->actdir = 0;
>> - }
>> - }
>> -
>> - return result;
>> -}
>> -
>> + FTW_FUNC_T ftw_func;
>> + NFTW_FUNC_T nftw_func;
>> +};
>> static int
>> -process_entry (struct ftw_data *data, struct dir_data *dir, const char *name,
>> - size_t namlen, int d_type)
>> +ftw_startup (const char *dir, bool is_nftw, union func_callback_t func,
>> + int descriptors, int flags)
>> {
>> - struct STRUCT_STAT st;
>> - int result = 0;
>> - int flag = 0;
>> - size_t new_buflen;
>> -
>> - if (name[0] == '.' && (name[1] == '\0'
>> - || (name[1] == '.' && name[2] == '\0')))
>> - /* Don't process the "." and ".." entries. */
>> - return 0;
>> -
>> - new_buflen = data->ftw.base + namlen + 2;
>> - if (data->dirbufsize < new_buflen
>> - && !ftw_allocate (data, 2 * new_buflen))
>> - return -1;
>> -
>> - *((char *) __mempcpy (data->dirbuf + data->ftw.base, name, namlen)) = '\0';
>> -
>> - int statres;
>> - if (dir->streamfd != -1)
>> - statres = FSTATAT (dir->streamfd, name, &st,
>> - (data->flags & FTW_PHYS) ? AT_SYMLINK_NOFOLLOW : 0);
>> - else
>> - {
>> - if ((data->flags & FTW_CHDIR) == 0)
>> - name = data->dirbuf;
>> -
>> - statres = ((data->flags & FTW_PHYS)
>> - ? LSTAT (name, &st)
>> - : STAT (name, &st));
>> - }
>> -
>> - if (statres < 0)
>> - {
>> - if (errno != EACCES && errno != ENOENT)
>> - result = -1;
>> - else if (data->flags & FTW_PHYS)
>> - flag = FTW_NS;
>> - else
>> - {
>> - /* Old code left ST undefined for dangling DT_LNK without
>> - FTW_PHYS set; a clarification at the POSIX level suggests
>> - it should contain information about the link (ala lstat).
>> - We do our best to fill in what data we can. */
>> - if (dir->streamfd != -1)
>> - statres = FSTATAT (dir->streamfd, name, &st,
>> - AT_SYMLINK_NOFOLLOW);
>> - else
>> - statres = LSTAT (name, &st);
>> - if (statres == 0 && S_ISLNK (st.st_mode))
>> - flag = FTW_SLN;
>> - else
>> - flag = FTW_NS;
>> - }
>> - }
>> - else
>> - {
>> - if (S_ISDIR (st.st_mode))
>> - flag = FTW_D;
>> - else if (S_ISLNK (st.st_mode))
>> - flag = FTW_SL;
>> - else
>> - flag = FTW_F;
>> - }
>> -
>> - if (result == 0
>> - && (flag == FTW_NS
>> - || !(data->flags & FTW_MOUNT) || st.st_dev == data->dev))
>> - {
>> - if (flag == FTW_D)
>> - {
>> - if ((data->flags & FTW_PHYS)
>> - || (!find_object (data, &st)
>> - /* Remember the object. */
>> - && (result = add_object (data, &st)) == 0))
>
> The old code was wrong here it seems, we would store a -1 to result no
> matter what?
Right, it did not occured to me. Also, I think we avoid such contructions
on if.
>
>> - result = ftw_dir (data, &st, dir);
>> - }
>> - else
>> - result = (*data->func) (data->dirbuf, &st, data->cvt_arr[flag],
>> - &data->ftw);
>> - }
>> -
>> - if ((data->flags & FTW_ACTIONRETVAL) && result == FTW_SKIP_SUBTREE)
>> - result = 0;
>> -
>> - return result;
>> -}
>> -
>> -
>> -static int
>> -__attribute ((noinline))
>> -ftw_dir (struct ftw_data *data, struct STRUCT_STAT *st, struct dir_data *old_dir)
>> -{
>> - struct dir_data dir;
>> - struct dirent64 *d;
>> - int previous_base = data->ftw.base;
>> - int result;
>> - char *startp;
>> -
>> - /* Open the stream for this directory. This might require that
>> - another stream has to be closed. */
>> - result = open_dir_stream (old_dir == NULL ? NULL : &old_dir->streamfd,
>> - data, &dir);
>> - if (result != 0)
>> - {
>> - if (errno == EACCES)
>> - /* We cannot read the directory. Signal this with a special flag. */
>> - result = (*data->func) (data->dirbuf, st, FTW_DNR, &data->ftw);
>> -
>> - return result;
>> - }
>> -
>> - /* First, report the directory (if not depth-first). */
>> - if (!(data->flags & FTW_DEPTH))
>> - {
>> - result = (*data->func) (data->dirbuf, st, FTW_D, &data->ftw);
>> - if (result != 0)
>> - {
>> - int save_err;
>> -fail:
>> - save_err = errno;
>> - __closedir (dir.stream);
>> - dir.streamfd = -1;
>> - __set_errno (save_err);
>> -
>> - if (data->actdir-- == 0)
>> - data->actdir = data->maxdir - 1;
>> - data->dirstreams[data->actdir] = NULL;
>> - return result;
>> - }
>> - }
>> -
>> - /* If necessary, change to this directory. */
>> - if (data->flags & FTW_CHDIR)
>> - {
>> - if (__fchdir (__dirfd (dir.stream)) < 0)
>> - {
>> - result = -1;
>> - goto fail;
>> - }
>> - }
>> -
>> - /* Next, update the `struct FTW' information. */
>> - ++data->ftw.level;
>> - startp = strchr (data->dirbuf, '\0');
>> - /* There always must be a directory name. */
>> - assert (startp != data->dirbuf);
>> - if (startp[-1] != '/')
>> - *startp++ = '/';
>> - data->ftw.base = startp - data->dirbuf;
>> -
>> - while (dir.stream != NULL && (d = __readdir64 (dir.stream)) != NULL)
>> - {
>> - int d_type = DT_UNKNOWN;
>> -#ifdef _DIRENT_HAVE_D_TYPE
>> - d_type = d->d_type;
>> -#endif
>> - result = process_entry (data, &dir, d->d_name, NAMLEN (d), d_type);
>> - if (result != 0)
>> - break;
>> - }
>> -
>> - if (dir.stream != NULL)
>> - {
>> - /* The stream is still open. I.e., we did not need more
>> - descriptors. Simply close the stream now. */
>> - int save_err = errno;
>> -
>> - assert (dir.content == NULL);
>> -
>> - __closedir (dir.stream);
>> - dir.streamfd = -1;
>> - __set_errno (save_err);
>> -
>> - if (data->actdir-- == 0)
>> - data->actdir = data->maxdir - 1;
>> - data->dirstreams[data->actdir] = NULL;
>> - }
>> - else
>> - {
>> - int save_err;
>> - char *runp = dir.content;
>> -
>> - while (result == 0 && *runp != '\0')
>> - {
>> - char *endp = strchr (runp, '\0');
>> -
>> - // XXX Should store the d_type values as well?!
>> - result = process_entry (data, &dir, runp, endp - runp, DT_UNKNOWN);
>> -
>> - runp = endp + 1;
>> - }
>> -
>> - save_err = errno;
>> - free (dir.content);
>> - __set_errno (save_err);
>> - }
>> -
>> - if ((data->flags & FTW_ACTIONRETVAL) && result == FTW_SKIP_SIBLINGS)
>> - result = 0;
>> -
>> - /* Prepare the return, revert the `struct FTW' information. */
>> - data->dirbuf[data->ftw.base - 1] = '\0';
>> - --data->ftw.level;
>> - data->ftw.base = previous_base;
>> -
>> - /* Finally, if we process depth-first report the directory. */
>> - if (result == 0 && (data->flags & FTW_DEPTH))
>> - result = (*data->func) (data->dirbuf, st, FTW_DP, &data->ftw);
>> -
>> - if (old_dir
>> - && (data->flags & FTW_CHDIR)
>> - && (result == 0
>> - || ((data->flags & FTW_ACTIONRETVAL)
>> - && (result != -1 && result != FTW_STOP))))
>> - {
>> - /* Change back to the parent directory. */
>> - int done = 0;
>> - if (old_dir->stream != NULL)
>> - if (__fchdir (__dirfd (old_dir->stream)) == 0)
>> - done = 1;
>> -
>> - if (!done)
>> - {
>> - if (data->ftw.base == 1)
>> - {
>> - if (__chdir ("/") < 0)
>> - result = -1;
>> - }
>> - else
>> - if (__chdir ("..") < 0)
>> - result = -1;
>> - }
>> - }
>> -
>> - return result;
>> -}
>> -
>> -
>> -static int
>> -__attribute ((noinline))
>> -ftw_startup (const char *dir, int is_nftw, void *func, int descriptors,
>> - int flags)
>> -{
>> - struct ftw_data data = { .dirstreams = NULL };
>> - struct STRUCT_STAT st;
>> - int result = 0;
>> - int save_err;
>> - int cwdfd = -1;
>> - char *cwd = NULL;
>> - char *cp;
>> -
>> /* First make sure the parameters are reasonable. */
>> if (dir[0] == '\0')
>> {
>> @@ -650,181 +105,310 @@ ftw_startup (const char *dir, int is_nftw, void *func, int descriptors,
>> return -1;
>> }
>> - data.maxdir = descriptors < 1 ? 1 : descriptors;
>> - data.actdir = 0;
>> - /* PATH_MAX is always defined when we get here. */
>> - if (!ftw_allocate (&data, MAX (2 * strlen (dir), PATH_MAX)))
>> + /* Data structure for keeping fingerprints of already processed
>> + object. This is needed when not using FTW_PHYS. */
>> + void *known_objects = NULL;
>> + /* Device of starting point. Needed for FTW_MOUNT. */
>> + dev_t root_dev = 0;
>> +
>> + /* NB: The fts FTS_LOGICAL implies on FTS_NOCHDIR, so to to proper implement
>> + FTW_CHDIR it requires manually manage the chdir / fchdir dance around
>> + the user's callback. The BUF is used to create the required path. */
>> + struct scratch_buffer buf;
>> + scratch_buffer_init (&buf);
>> +
>> + int start_fd = __open (".", O_RDONLY | O_CLOEXEC);
>> + if (start_fd < -1)
>> return -1;
>> - memset (data.dirstreams, '\0', data.maxdir * sizeof (struct dir_data *));
>> - cp = __stpcpy (data.dirbuf, dir);
>> - /* Strip trailing slashes. */
>> - while (cp > data.dirbuf + 1 && cp[-1] == '/')
>> - --cp;
>> - *cp = '\0';
>> - data.ftw.level = 0;
>> + int fts_options = 0;
>> + if (flags & FTW_PHYS)
>> + fts_options |= FTS_PHYSICAL;
>> + else
>> + fts_options |= FTS_LOGICAL;
>> - /* Find basename. */
>> - while (cp > data.dirbuf && cp[-1] != '/')
>> - --cp;
>> - data.ftw.base = cp - data.dirbuf;
>> + if (flags & FTW_MOUNT)
>> + fts_options |= FTS_XDEV;
>> - data.flags = flags;
>> + if (!(flags & FTW_CHDIR))
>> + fts_options |= FTS_NOCHDIR;
>> - /* This assignment might seem to be strange but it is what we want.
>> - The trick is that the first three arguments to the `ftw' and
>> - `nftw' callback functions are equal. Therefore we can call in
>> - every case the callback using the format of the `nftw' version
>> - and get the correct result since the stack layout for a function
>> - call in C allows this. */
>> - data.func = (NFTW_FUNC_T) func;
>> + char *const paths[] = { (char *)dir, NULL };
>> - /* Since we internally use the complete set of FTW_* values we need
>> - to reduce the value range before calling a `ftw' callback. */
>> - data.cvt_arr = is_nftw ? nftw_arr : ftw_arr;
>> -
>> - /* No object known so far. */
>> - data.known_objects = NULL;
>> -
>> - /* Now go to the directory containing the initial file/directory. */
>> - if (flags & FTW_CHDIR)
>> + /* NB: ingnore 'descriptors' limits since fts manages fds dynamically. */
>> + FTS_TYPE *ftsp = FTS_OPEN (paths, fts_options, NULL);
>> + if (!ftsp)
>> {
>> - /* We have to be able to go back to the current working
>> - directory. The best way to do this is to use a file
>> - descriptor. */
>> - cwdfd = __open (".", O_RDONLY | O_DIRECTORY);
>> - if (cwdfd == -1)
>> - {
>> - /* Try getting the directory name. This can be needed if
>> - the current directory is executable but not readable. */
>> - if (errno == EACCES)
>> - /* GNU extension ahead. */
>> - cwd = __getcwd (NULL, 0);
>> -
>> - if (cwd == NULL)
>> - goto out_fail;
>> - }
>> - else if (data.maxdir > 1)
>> - /* Account for the file descriptor we use here. */
>> - --data.maxdir;
>> -
>> - if (data.ftw.base > 0)
>> - {
>> - /* Change to the directory the file is in. In data.dirbuf
>> - we have a writable copy of the file name. Just NUL
>> - terminate it for now and change the directory. */
>> - if (data.ftw.base == 1)
>> - /* I.e., the file is in the root directory. */
>> - result = __chdir ("/");
>> - else
>> - {
>> - char ch = data.dirbuf[data.ftw.base - 1];
>> - data.dirbuf[data.ftw.base - 1] = '\0';
>> - result = __chdir (data.dirbuf);
>> - data.dirbuf[data.ftw.base - 1] = ch;
>> - }
>> - }
>> + __close (start_fd);
>> + return -1;
>> }
>> - /* Get stat info for start directory. */
>> - if (result == 0)
>> + FTSENT_TYPE *ent = NULL;
>> + int rc = 0;
>
> Should this be set to -1 unless we have a success?
I don't think it would matter much because it uses 'rc' to track the callback
result as well.
>
>> + int save_err;
>> +
>> + bool postorder = (flags & FTW_DEPTH) != 0;
>> +
>> + /* Used to proper support FTW_SKIP_SIBLINGS to avoid call fts_read again
>> + of the next iteration. */
>> + bool skip_read = false;
>> +
>> + while (true)
>> {
>> - const char *name;
>> + if (!skip_read)
>> + {
>> + errno = 0;
>> + ent = FTS_READ (ftsp);
>> + }
>> + skip_read = false;
>> - if (data.flags & FTW_CHDIR)
>> + if (ent == NULL)
>> {
>> - name = data.dirbuf + data.ftw.base;
>> - if (name[0] == '\0')
>> - name = ".";
>> - }
>> - else
>> - name = data.dirbuf;
>> + if (errno != 0)
>> + goto done;
>> + break;
>> + }
>> - if (((flags & FTW_PHYS)
>> - ? LSTAT (name, &st)
>> - : STAT (name, &st)) < 0)
>> + if (ent->fts_level == FTS_ROOTLEVEL)
>> {
>> - if (!(flags & FTW_PHYS)
>> - && errno == ENOENT
>> - && LSTAT (name, &st) == 0
>> - && S_ISLNK (st.st_mode))
>> - result = (*data.func) (data.dirbuf, &st, data.cvt_arr[FTW_SLN],
>> - &data.ftw);
>> - else
>> - /* No need to call the callback since we cannot say anything
>> - about the object. */
>> - result = -1;
>> - }
>> - else
>> - {
>> - if (S_ISDIR (st.st_mode))
>> + /* If the STARTING path cannot be accessed, nftw must fail rather
>> + than calling the callback with FTW_NS (it is required only for
>> + FTS_ROOTLEVEL). */
>> + if (ent->fts_info == FTS_NS && ent->fts_errno != 0)
>> {
>> - /* Remember the device of the initial directory in case
>> - FTW_MOUNT is given. */
>> - data.dev = st.st_dev;
>> + rc = -1;
>> + errno = ent->fts_errno;
>> + goto done;
>> + }
>> - /* We know this directory now. */
>> - if (!(flags & FTW_PHYS))
>> - result = add_object (&data, &st);
>> + /* Remember the device of the initial directory in case FTW_MOUNT. */
>> + if (ent->fts_statp)
>> + root_dev = ent->fts_statp->st_dev;
>> + }
>> - if (result == 0)
>> - result = ftw_dir (&data, &st, NULL);
>> + /* Handle FTW_MOUNT. */
>> + bool is_mount_crossing = false;
>> + if ((flags & FTW_MOUNT) && ent->fts_level > FTS_ROOTLEVEL
>> + && ent->fts_statp)
>> + if (ent->fts_statp->st_dev != root_dev)
>> + is_mount_crossing = true;
>> +
>> + int fn_flag;
>> + switch (ent->fts_info)
>> + {
>> + case FTS_D:
>> + /* Remember the device of the initial directory in case FTW_MOUNT
>> + is given. */
>> + if (ent->fts_level == FTS_ROOTLEVEL)
>> + {
>> + if (!(flags & FTW_PHYS)
>> + && add_object (&known_objects, ent->fts_statp) == -1)
>
> OK. Check -1 failure.
>
> Does this fail to set rc to -1?
It does, it will update it.
>
>> + goto done;
>> }
>> else
>> {
>> - int flag = S_ISLNK (st.st_mode) ? FTW_SL : FTW_F;
>> -
>> - result = (*data.func) (data.dirbuf, &st, data.cvt_arr[flag],
>> - &data.ftw);
>> + if (!(flags & FTW_PHYS)
>> + && find_object (&known_objects, ent->fts_statp))
>> + continue;
>> + if (add_object (&known_objects, ent->fts_statp) == -1)
>
> OK. Check -1 failure.
>
> Does this fail to set rc to -1?
Yes, I will fix it.
>
>> + goto done;
>> }
>> - }
>> - if ((flags & FTW_ACTIONRETVAL)
>> - && (result == FTW_SKIP_SUBTREE || result == FTW_SKIP_SIBLINGS))
>> - result = 0;
>> + /* Normally we skip FTS_D in depth mode, but for a mount
>> + crossing, fts (FTS_XDEV) will NOT descend. We will never
>> + get FTS_DP. We must handle it manually. */
>> + if (postorder && !is_mount_crossing)
>> + continue;
>> +
>> + /* fts returns FTS_D for directories it has not tried to open yet,
>> + even if it has not read permissions. The nftw must report
>> + FTW_DNR instead. */
>> + if (__faccessat (start_fd, ent->fts_accpath, R_OK, AT_EACCESS) == 0)
>> + fn_flag = FTW_D;
>> + else
>> + {
>> + fn_flag = FTW_DNR;
>> + FTS_SET (ftsp, ent, FTS_SKIP);
>> + }
>> + break;
>> +
>> + case FTS_DP:
>> + if (!postorder)
>> + continue;
>> + fn_flag = FTW_DP;
>> + break;
>> +
>> + case FTS_DEFAULT:
>> + case FTS_F:
>> + fn_flag = FTW_F;
>> + break;
>> +
>> + case FTS_SL:
>> + fn_flag = FTW_SL;
>> + break;
>> +
>> + case FTS_SLNONE:
>> + fn_flag = FTW_SLN;
>> + break;
>> +
>> + case FTS_DNR:
>> + fn_flag = FTW_DNR;
>> + break;
>> +
>> + case FTS_NS:
>> + fn_flag = FTW_NS;
>> + break;
>> +
>> + case FTS_DC:
>> + if (S_ISDIR (ent->fts_statp->st_mode))
>> + {
>> + if (!(flags & FTW_PHYS)
>> + && find_object (&known_objects, ent->fts_statp))
>> + continue;
>> + if (add_object (&known_objects, ent->fts_statp) == -1)
>
> OK. Check -1 failure.
>
> Does this fail to set rc to -1?
Yes, I will fix it.
>
>> + goto done;
>> + }
>> + /* A directory cycle was detected (Logical walk only) and
>> + instead of aborting with ELOOP, we report this as a
>> + symlink that cannot be successfully followed (FTW_SLN). */
>> + fn_flag = FTW_SLN;
>> + break;
>> +
>> + default:
>> + rc = -1;
>> + goto done;
>> + }
>> +
>> + struct FTW ftw_data = {
>> + .base = ent->fts_pathlen - ent->fts_namelen,
>> + .level = ent->fts_level
>> + };
>> +
>> + bool chdir_performed = false;
>> + if (flags & FTW_CHDIR)
>> + {
>> + const char *target_dir = NULL;
>> +
>> + size_t dir_len = ent->fts_pathlen - ent->fts_namelen;
>> + if (dir_len == 0)
>> + /* Case: "filename" (Root level file/dir with no path prefix):
>> + the containing directory is the start directory. */
>> + target_dir = ".";
>> + else
>> + {
>> + /* Case: "path/to/filename": we need to extract "path/to" */
>> + if (!scratch_buffer_set_array_size (&buf, dir_len, 1))
>> + goto done;
>> + memcpy (buf.data, ent->fts_accpath, dir_len);
>> + char *target = buf.data;
>> + if (dir_len > 1 && target[dir_len-1] == '/')
>> + target[dir_len-1] = '\0';
>> + target_dir = target;
>> + }
>> +
>> + if (__chdir (target_dir) == 0)
>> + chdir_performed = true;
>> + else
>> + {
>> + rc = -1;
>> + goto done;
>> + }
>> + }
>> +
>> + rc = is_nftw
>> + ? func.nftw_func (ent->fts_path, ent->fts_statp, fn_flag, &ftw_data)
>> + : func.ftw_func (ent->fts_path, ent->fts_statp, fn_flag);
>> +
>> + if (chdir_performed && __fchdir (start_fd) != 0)
>> + {
>> + rc = -1;
>> + goto done;
>> + }
>> +
>> + if (!(flags & FTW_ACTIONRETVAL))
>> + {
>> + if (rc != 0)
>> + break;
>> + continue;
>> + }
>> + /* FTW_ACTIONRETVAL support. */
>> + switch (rc)
>> + {
>> + case FTW_CONTINUE:
>> + /* Default behavior: just proceed to next entry. */
>> + break;
>> +
>> + case FTW_STOP:
>> + /* Stop traversal immediately (success return code). We set rc=0
>> + here because FTW_STOP is considered a "successful" stop,
>> + unlike a non-zero return in standard mode. */
>> + rc = 0;
>> + goto done;
>> +
>> + case FTW_SKIP_SUBTREE:
>> + /* Only meaningful if we are currently visiting a directory in
>> + pre-order (FTS_D). */
>> + if (ent->fts_info == FTS_D)
>> + FTS_SET (ftsp, ent, FTS_SKIP);
>> + break;
>> +
>> + case FTW_SKIP_SIBLINGS:
>> + /* We must skip everything until we emerge at a lower level
>> + (parent). */
>> + {
>> + int current_level = ent->fts_level;
>> +
>> + /* Drain fts until level < current. */
>> + while ((ent = FTS_READ (ftsp)) != NULL)
>> + {
>> + if (ent->fts_level < current_level)
>> + {
>> + skip_read = true;
>> + break;
>> + }
>> + }
>> +
>> + if (ent == NULL)
>> + goto done;
>> + } break;
>> +
>> + default:
>> + rc = 0;
>> + goto done;
>> + }
>> }
>> - /* Return to the start directory (if necessary). */
>> - if (cwdfd != -1)
>> - {
>> - int save_err = errno;
>> - __fchdir (cwdfd);
>> - __close_nocancel_nostatus (cwdfd);
>> - __set_errno (save_err);
>> - }
>> - else if (cwd != NULL)
>> - {
>> - int save_err = errno;
>> - __chdir (cwd);
>> - free (cwd);
>> - __set_errno (save_err);
>> - }
>> -
>> - /* Free all memory. */
>> - out_fail:
>> +done:
>> + scratch_buffer_free (&buf);
>> + __tdestroy (known_objects, free);
>> save_err = errno;
>> - __tdestroy (data.known_objects, free);
>> - free (data.dirstreams);
>> + FTS_CLOSE (ftsp);
>> + __close (start_fd);
>> __set_errno (save_err);
>> - return result;
>> + return rc;
>> }
>> -
>> -
>> /* Entry points. */
>> int
>> FTW_NAME (const char *path, FTW_FUNC_T func, int descriptors)
>> {
>> - return ftw_startup (path, 0, func, descriptors, 0);
>> + return ftw_startup (path,
>> + false,
>> + (union func_callback_t) { .ftw_func = func },
>> + descriptors,
>> + 0);
>> }
>> #ifndef NFTW_OLD_NAME
>> int
>> NFTW_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags)
>> {
>> - return ftw_startup (path, 1, func, descriptors, flags);
>> + return ftw_startup (path, true,
>> + (union func_callback_t) { .nftw_func = func },
>> + descriptors,
>> + flags);
>> }
>> #else
>> @@ -841,7 +425,11 @@ NFTW_NEW_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags)
>> __set_errno (EINVAL);
>> return -1;
>> }
>> - return ftw_startup (path, 1, func, descriptors, flags);
>> + return ftw_startup (path,
>> + true,
>> + (union func_callback_t) { .nftw_func = func },
>> + descriptors,
>> + flags);
>> }
>> versioned_symbol (libc, NFTW_NEW_NAME, NFTW_NAME, GLIBC_2_3_3);
>> @@ -856,7 +444,11 @@ attribute_compat_text_section
>> NFTW_OLD_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags)
>> {
>> flags &= (FTW_PHYS | FTW_MOUNT | FTW_CHDIR | FTW_DEPTH);
>> - return ftw_startup (path, 1, func, descriptors, flags);
>> + return ftw_startup (path,
>> + true,
>> + (union func_callback_t) { .nftw_func = func },
>> + descriptors,
>> + flags);
>> }
>> compat_symbol (libc, NFTW_OLD_NAME, NFTW_NAME, GLIBC_2_1);
>> diff --git a/io/ftw64-time64.c b/io/ftw64-time64.c
>> index 2df871f802..517dc93d92 100644
>> --- a/io/ftw64-time64.c
>> +++ b/io/ftw64-time64.c
>> @@ -23,11 +23,15 @@
>> # define NFTW_NAME __nftw64_time64
>> # define INO_T ino64_t
>> # define STRUCT_STAT __stat64_t64
>> -# define LSTAT __lstat64_time64
>> -# define STAT __stat64_time64
>> -# define FSTATAT __fstatat64_time64
>> # define FTW_FUNC_T __ftw64_time64_func_t
>> # define NFTW_FUNC_T __nftw64_time64_func_t
>> +# define FTS_TYPE FTS64_TIME64
>> +# define FTSENT_TYPE FSTENT64_TIME64
>> +# define FTS_OPEN __fts64_open_time64
>> +# define FTS_READ __fts64_read_time64
>> +# define FTS_SET __fts64_set_time64
>> +# define FTS_CLOSE __fts64_close_time64
>> +
>> # include "ftw.c"
>> #endif
>> diff --git a/io/ftw64.c b/io/ftw64.c
>> index 0d7cb30091..b61b0e24b8 100644
>> --- a/io/ftw64.c
>> +++ b/io/ftw64.c
>> @@ -22,10 +22,14 @@
>> #define NFTW_NEW_NAME __new_nftw64
>> #define INO_T ino64_t
>> #define STRUCT_STAT stat64
>> -#define LSTAT __lstat64
>> -#define STAT __stat64
>> -#define FSTATAT __fstatat64
>> #define FTW_FUNC_T __ftw64_func_t
>> #define NFTW_FUNC_T __nftw64_func_t
>> +#define FTS_TYPE FTS64
>> +#define FTSENT_TYPE FTSENT64
>> +#define FTS_OPEN __fts64_open
>> +#define FTS_READ __fts64_read
>> +#define FTS_SET __fts64_set
>> +#define FTS_CLOSE __fts64_close
>> +
>> #include "ftw.c"
>> diff --git a/io/ftwtest-sh b/io/ftwtest-sh
>> index 9758e18f0d..a8e5160210 100644
>> --- a/io/ftwtest-sh
>> +++ b/io/ftwtest-sh
>> @@ -62,6 +62,8 @@ ln -s $tmpdir/foo/lvl1/lvl2 $tmpdir/foo/lvl1/lvl2/link@2
>> ln -s $tmpdir/foo/lvl1/lvl2/lvl3/lvl4 $tmpdir/foo/lvl1/link@1
>> echo > $tmpdir/bar/xo
>> chmod a-x,a+r $tmpdir/bar
>> +mkdir $tmpdir/ndir
>> +chmod a-r $tmpdir/ndir
>> testout=$(mktemp $tmp/ftwtest-tmp-XXXXXX.out)
>> @@ -73,6 +75,7 @@ base = "$tmp/", file = "$ftwtest", flag = FTW_D, level = 0
>> base = "$tmp/$ftwtest/", file = "bar", flag = FTW_D, level = 1
>> base = "$tmp/$ftwtest/", file = "baz", flag = FTW_F, level = 1
>> base = "$tmp/$ftwtest/", file = "foo", flag = FTW_D, level = 1
>> +base = "$tmp/$ftwtest/", file = "ndir", flag = FTW_DNR, level = 1
>> base = "$tmp/$ftwtest/bar/", file = "xo", flag = FTW_NS, level = 2
>> base = "$tmp/$ftwtest/foo/", file = "lvl1", flag = FTW_D, level = 2
>> base = "$tmp/$ftwtest/foo/lvl1/", file = "file@1", flag = FTW_F, level = 3
>> @@ -92,6 +95,7 @@ base = "$tmp/", file = "$ftwtest", flag = FTW_DP, level = 0
>> base = "$tmp/$ftwtest/", file = "bar", flag = FTW_DP, level = 1
>> base = "$tmp/$ftwtest/", file = "baz", flag = FTW_F, level = 1
>> base = "$tmp/$ftwtest/", file = "foo", flag = FTW_DP, level = 1
>> +base = "$tmp/$ftwtest/", file = "ndir", flag = FTW_DNR, level = 1
>> base = "$tmp/$ftwtest/bar/", file = "xo", flag = FTW_NS, level = 2
>> base = "$tmp/$ftwtest/foo/", file = "lvl1", flag = FTW_DP, level = 2
>> base = "$tmp/$ftwtest/foo/lvl1/", file = "file@1", flag = FTW_F, level = 3
>> @@ -111,6 +115,7 @@ base = "$tmp/", file = "$ftwtest", flag = FTW_D, level = 0
>> base = "$tmp/$ftwtest/", file = "bar", flag = FTW_D, level = 1
>> base = "$tmp/$ftwtest/", file = "baz", flag = FTW_F, level = 1
>> base = "$tmp/$ftwtest/", file = "foo", flag = FTW_D, level = 1
>> +base = "$tmp/$ftwtest/", file = "ndir", flag = FTW_DNR, level = 1
>> base = "$tmp/$ftwtest/bar/", file = "xo", flag = FTW_NS, level = 2
>> base = "$tmp/$ftwtest/foo/", file = "lvl1", flag = FTW_D, level = 2
>> base = "$tmp/$ftwtest/foo/lvl1/", file = "file@1", flag = FTW_F, level = 3
>> @@ -125,7 +130,9 @@ EOF
>> rm $testout
>> # For the next test everything must be readable.
>> +chmod a+r $tmpdir/ndir
>> chmod -fR a+x $tmpdir
>> +chmod a-r $tmpdir/ndir
>> $testprogram --chdir $tmpdir |
>> sort > $testout
>> @@ -138,6 +145,7 @@ base = "$tmp/", file = "$ftwtest", flag = FTW_D, cwd = $tmpreal, level = 0
>> base = "$tmp/$ftwtest/", file = "bar", flag = FTW_D, cwd = $tmpreal/$ftwtest, level = 1
>> base = "$tmp/$ftwtest/", file = "baz", flag = FTW_F, cwd = $tmpreal/$ftwtest, level = 1
>> base = "$tmp/$ftwtest/", file = "foo", flag = FTW_D, cwd = $tmpreal/$ftwtest, level = 1
>> +base = "$tmp/$ftwtest/", file = "ndir", flag = FTW_DNR, cwd = $tmpreal/$ftwtest, level = 1
>> base = "$tmp/$ftwtest/bar/", file = "xo", flag = FTW_F, cwd = $tmpreal/$ftwtest/bar, level = 2
>> base = "$tmp/$ftwtest/foo/", file = "lvl1", flag = FTW_D, cwd = $tmpreal/$ftwtest/foo, level = 2
>> base = "$tmp/$ftwtest/foo/lvl1/", file = "file@1", flag = FTW_F, cwd = $tmpreal/$ftwtest/foo/lvl1, level = 3
>> @@ -160,6 +168,7 @@ base = "", file = "$ftwtest", flag = FTW_D, cwd = $tmpreal, level = 0
>> base = "$ftwtest/", file = "bar", flag = FTW_D, cwd = $tmpreal/$ftwtest, level = 1
>> base = "$ftwtest/", file = "baz", flag = FTW_F, cwd = $tmpreal/$ftwtest, level = 1
>> base = "$ftwtest/", file = "foo", flag = FTW_D, cwd = $tmpreal/$ftwtest, level = 1
>> +base = "$ftwtest/", file = "ndir", flag = FTW_DNR, cwd = $tmpreal/$ftwtest, level = 1
>> base = "$ftwtest/bar/", file = "xo", flag = FTW_F, cwd = $tmpreal/$ftwtest/bar, level = 2
>> base = "$ftwtest/foo/", file = "lvl1", flag = FTW_D, cwd = $tmpreal/$ftwtest/foo, level = 2
>> base = "$ftwtest/foo/lvl1/", file = "file@1", flag = FTW_F, cwd = $tmpreal/$ftwtest/foo/lvl1, level = 3
>> @@ -182,6 +191,7 @@ base = "$ftwtest/", file = ".", flag = FTW_D, cwd = $tmpreal/$ftwtest, level = 0
>> base = "$ftwtest/./", file = "bar", flag = FTW_D, cwd = $tmpreal/$ftwtest, level = 1
>> base = "$ftwtest/./", file = "baz", flag = FTW_F, cwd = $tmpreal/$ftwtest, level = 1
>> base = "$ftwtest/./", file = "foo", flag = FTW_D, cwd = $tmpreal/$ftwtest, level = 1
>> +base = "$ftwtest/./", file = "ndir", flag = FTW_DNR, cwd = $tmpreal/$ftwtest, level = 1
>> base = "$ftwtest/./bar/", file = "xo", flag = FTW_F, cwd = $tmpreal/$ftwtest/bar, level = 2
>> base = "$ftwtest/./foo/", file = "lvl1", flag = FTW_D, cwd = $tmpreal/$ftwtest/foo, level = 2
>> base = "$ftwtest/./foo/lvl1/", file = "file@1", flag = FTW_F, cwd = $tmpreal/$ftwtest/foo/lvl1, level = 3
>> @@ -226,6 +236,7 @@ base = "$tmp/", file = "$ftwtest", flag = FTW_D, level = 0
>> base = "$tmp/$ftwtest/", file = "bar", flag = FTW_D, level = 1
>> base = "$tmp/$ftwtest/", file = "baz", flag = FTW_F, level = 1
>> base = "$tmp/$ftwtest/", file = "foo", flag = FTW_D, level = 1
>> +base = "$tmp/$ftwtest/", file = "ndir", flag = FTW_DNR, level = 1
>> base = "$tmp/$ftwtest/bar/", file = "xo", flag = FTW_F, level = 2
>> base = "$tmp/$ftwtest/foo/", file = "lvl1", flag = FTW_D, level = 2
>> base = "$tmp/$ftwtest/foo/", file = "lvl1b", flag = FTW_D, level = 2
>> @@ -250,6 +261,7 @@ base = "$tmp/", file = "$ftwtest", flag = FTW_D, level = 0
>> base = "$tmp/$ftwtest/", file = "bar", flag = FTW_D, level = 1
>> base = "$tmp/$ftwtest/", file = "baz", flag = FTW_F, level = 1
>> base = "$tmp/$ftwtest/", file = "foo", flag = FTW_D, level = 1
>> +base = "$tmp/$ftwtest/", file = "ndir", flag = FTW_DNR, level = 1
>> base = "$tmp/$ftwtest/bar/", file = "xo", flag = FTW_F, level = 2
>> base = "$tmp/$ftwtest/foo/", file = "lvl1", flag = FTW_D, level = 2
>> base = "$tmp/$ftwtest/foo/", file = "lvl1b", flag = FTW_D, level = 2
>> @@ -275,6 +287,7 @@ base = "$tmp/", file = "$ftwtest", flag = FTW_D, level = 0
>> base = "$tmp/$ftwtest/", file = "bar", flag = FTW_D, level = 1
>> base = "$tmp/$ftwtest/", file = "baz", flag = FTW_F, level = 1
>> base = "$tmp/$ftwtest/", file = "foo", flag = FTW_D, level = 1
>> +base = "$tmp/$ftwtest/", file = "ndir", flag = FTW_DNR, level = 1
>> base = "$tmp/$ftwtest/bar/", file = "xo", flag = FTW_F, level = 2
>> base = "$tmp/$ftwtest/foo/", file = "lvl1", flag = FTW_D, level = 2
>> base = "$tmp/$ftwtest/foo/", file = "lvl1b", flag = FTW_D, level = 2
>> diff --git a/io/tst-nftw-bz33882.c b/io/tst-nftw-bz33882.c
>> new file mode 100644
>> index 0000000000..d2a7b6a678
>> --- /dev/null
>> +++ b/io/tst-nftw-bz33882.c
>> @@ -0,0 +1,88 @@
>> +/* Check if nested directory level does not overflow the stack (BZ #33882)
>> + Copyright (C) 2026 Free Software Foundation, Inc.
>> + This file is part of the GNU C Library.
>> +
>> + The GNU C Library is free software; you can redistribute it and/or
>> + modify it under the terms of the GNU Lesser General Public
>> + License as published by the Free Software Foundation; either
>> + version 2.1 of the License, or (at your option) any later version.
>> +
>> + The GNU C Library is distributed in the hope that it will be useful,
>> + but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
>> + Lesser General Public License for more details.
>> +
>> + You should have received a copy of the GNU Lesser General Public
>> + License along with the GNU C Library; if not, see
>> + <https://www.gnu.org/licenses/>. */
>> +
>> +#include <ftw.h>
>> +#include <stdio.h>
>> +#include <stdlib.h>
>> +#include <support/check.h>
>> +#include <support/support.h>
>> +#include <support/temp_file.h>
>> +#include <support/xunistd.h>
>> +#include <sys/resource.h>
>> +
>> +/* Typical stack frame for a recursive function is 64–256 bytes, with a nested
>> + depth of 5000 would required around 640Kb of stack space. */
>> +enum { nested_depth = 5000 };
>> +enum { stack_limit_kb = 512 };
>> +
>> +/* Short name to maximize depth/path ratio. */
>> +static const char dir_name[] = "d";
>> +
>> +static void
>> +do_cleanup (void)
>> +{
>> + xchdir ("..");
>> + for (int i = 0; i < nested_depth; i++)
>> + {
>> + remove (dir_name);
>> + xchdir ("..");
>> + }
>> + remove (dir_name);
>> +}
>> +#define CLEANUP_HANDLER do_cleanup
>> +
>> +static int
>> +my_func (const char *file, const struct stat *sb, int flag, struct FTW *ftwbuf)
>> +{
>> + return 0;
>> +}
>> +
>> +/* Set the RLIMIT_AS limit to the value in *LIMIT. */
>> +static void
>> +xsetrlimit_stack (const struct rlimit *limit)
>> +{
>> + if (setrlimit (RLIMIT_STACK, limit) != 0)
>> + FAIL_EXIT1 ("setrlimit (RLIMIT_STACK, %lu): %m",
>> + (unsigned long) limit->rlim_cur);
>> +}
>> +
>> +static int
>> +do_test (void)
>> +{
>> + xsetrlimit_stack (&(struct rlimit) { .rlim_cur = stack_limit_kb * 1024,
>> + .rlim_max = stack_limit_kb * 1024 });
>> +
>> + char *tempdir = support_create_temp_directory ("tst-bz33882");
>> +
>> + xchdir (tempdir);
>> + for (int i = 0; i < nested_depth; i++)
>> + {
>> + xmkdir (dir_name, 0700);
>> + xchdir (dir_name);
>> + }
>> +
>> + TEST_COMPARE (nftw (tempdir, my_func, 20, 0), 0);
>> +
>> + free (tempdir);
>> +
>> + do_cleanup ();
>> +
>> + return 0;
>> +}
>> +
>> +#include <support/test-driver.c>
>> diff --git a/sysdeps/wordsize-64/fts.c b/sysdeps/wordsize-64/fts.c
>> index 159dc1febe..b10d81714a 100644
>> --- a/sysdeps/wordsize-64/fts.c
>> +++ b/sysdeps/wordsize-64/fts.c
>> @@ -12,8 +12,8 @@
>> #undef fts64_set
>> #undef fts64_children
>> -weak_alias (fts_open, fts64_open)
>> -weak_alias (fts_close, fts64_close)
>> -weak_alias (fts_read, fts64_read)
>> -weak_alias (fts_set, fts64_set)
>> -weak_alias (fts_children, fts64_children)
>> +weak_alias (__fts_open, fts64_open)
>> +weak_alias (__fts_close, fts64_close)
>> +weak_alias (__fts_read, fts64_read)
>> +weak_alias (__fts_set, fts64_set)
>> +weak_alias (__fts_children, fts64_children)
>
>
More information about the Libc-alpha
mailing list