[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