[PATCH 2/3] io: Consolidate ftw implementation
Adhemerval Zanella Netto
adhemerval.zanella@linaro.org
Fri Apr 17 17:31:16 GMT 2026
This is a mechanical patch and I install if no one opposes it.
On 17/04/26 10:24, Adhemerval Zanella wrote:
> Remove wordsize-64 and arch-specific implementations, for ABIs when
> off_t is the same as off64_t (__OFF_T_MATCHES_OFF64_T) the ftw64.c
> will create the requires aliases.
>
> The ftw.c implementation is moved to ftw-common.c to simplify
> the __OFF_T_MATCHES_OFF64_T usage.
> ---
> io/ftw-common.c | 969 ++++++++++++++++++
> io/ftw.c | 958 +----------------
> io/ftw64-time64.c | 2 +-
> io/ftw64.c | 28 +-
> sysdeps/unix/sysv/linux/mips/mips64/n64/ftw.c | 1 -
> .../unix/sysv/linux/mips/mips64/n64/ftw64.c | 1 -
> sysdeps/unix/sysv/linux/x86_64/x32/ftw.c | 1 -
> sysdeps/unix/sysv/linux/x86_64/x32/ftw64.c | 1 -
> sysdeps/wordsize-64/ftw.c | 16 -
> sysdeps/wordsize-64/ftw64.c | 1 -
> 10 files changed, 1003 insertions(+), 975 deletions(-)
> create mode 100644 io/ftw-common.c
> delete mode 100644 sysdeps/unix/sysv/linux/mips/mips64/n64/ftw.c
> delete mode 100644 sysdeps/unix/sysv/linux/mips/mips64/n64/ftw64.c
> delete mode 100644 sysdeps/unix/sysv/linux/x86_64/x32/ftw.c
> delete mode 100644 sysdeps/unix/sysv/linux/x86_64/x32/ftw64.c
> delete mode 100644 sysdeps/wordsize-64/ftw.c
> delete mode 100644 sysdeps/wordsize-64/ftw64.c
>
> diff --git a/io/ftw-common.c b/io/ftw-common.c
> new file mode 100644
> index 0000000000..07df0ab25a
> --- /dev/null
> +++ b/io/ftw-common.c
> @@ -0,0 +1,969 @@
> +/* File tree walker functions.
> + Copyright (C) 1996-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 <assert.h>
> +#include <dirent.h>
> +#include <fcntl.h>
> +#include <ftw.h>
> +#include <not-cancel.h>
> +#include <search.h>
> +#include <unistd.h>
> +#include <sys/param.h>
> +
> +#define NAMLEN(dirent) _D_EXACT_NAMLEN (dirent)
> +
> +/* Support for the LFS API version. */
> +#ifndef FTW_NAME
> +# define FTW_NAME ftw
> +# define NFTW_NAME nftw
> +# define NFTW_OLD_NAME __old_nftw
> +# define NFTW_NEW_NAME __new_nftw
> +# define INO_T ino_t
> +# define STRUCT_STAT stat
> +# define LSTAT __lstat
> +# define STAT __stat
> +# define FSTATAT __fstatat
> +# 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
> +#endif
> +
> +struct dir_data
> +{
> + DIR *stream;
> + int streamfd;
> + char *content;
> +};
> +
> +struct known_object
> +{
> + dev_t dev;
> + INO_T ino;
> +};
> +
> +/* Represents the execution state of a directory processing frame within the
> + iterative file tree walk loop.
> +
> + Because the tree traversal is implemented iteratively using a custom stack
> + rather than standard recursion, this state machine tracks the progress
> + of each directory currently being visited. */
> +enum ftw_frame_state
> +{
> + /* The initial state of a newly pushed directory frame. Attempts to open
> + the directory stream. If successful, transitions to
> + FTW_STATE_STREAM_LOOP. */
> + FTW_STATE_INIT = 0,
> +
> + /* Iterating over the directory entries directly from the open DIR stream
> + (using readdir). If a subdirectory is encountered and needs to be
> + descended into, a new frame is added to the stack and execution pauses
> + here. Transitions to FTW_STATE_CONTENT_LOOP if the stream was closed
> + and cached to free up file descriptors, or FTW_STATE_CLEANUP when
> + done. */
> + FTW_STATE_STREAM_LOOP,
> +
> + /* Iterating over directory entries from a cached memory buffer. This state
> + is used as a fallback when the original DIR stream had to be closed
> + prematurely to prevent file descriptor exhaustion while descending into
> + deeply nested child directories. Transitions to FTW_STATE_CLEANUP when
> + all cached entries are processed. */
> + FTW_STATE_CONTENT_LOOP,
> +
> + /* The final state, handles resource deallocation (closing remaining
> + streams, freeing cached content buffers), triggering post-traversal
> + callbacks (like FTW_DP for FTW_DEPTH walks), and restoring the
> + previous working directory if FTW_CHDIR was used. */
> + FTW_STATE_CLEANUP
> +};
> +
> +/* Keep track of visited directories. */
> +struct ftw_frame
> +{
> + struct dir_data dir;
> + struct STRUCT_STAT st;
> + int previous_base;
> + char *runp;
> + enum ftw_frame_state state;
> +};
> +
> +struct ftw_stack
> +{
> + struct ftw_frame **stack;
> + size_t num_blocks;
> + ssize_t top;
> +};
> +
> +typedef union
> +{
> + NFTW_FUNC_T nftw_func;
> + FTW_FUNC_T ftw_func;
> +} func_callback_t;
> +
> +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. */
> + bool is_nftw;
> + func_callback_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;
> +};
> +#define CALL_FUNC(__ftw_data, __fp, __sb, __f, __ftw) \
> + ((__ftw_data)->is_nftw ? (__ftw_data)->func.nftw_func (__fp, __sb, __f, __ftw) \
> + : (__ftw_data)->func.ftw_func (__fp, __sb, __f))
> +
> +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
> +};
> +
> +
> +static int
> +object_compare (const void *p1, const void *p2)
> +{
> + /* We don't need a sophisticated and useful comparison. We are only
> + interested in equality. However, we must be careful not to
> + accidentally compare `holes' in the structure. */
> + const struct known_object *kp1 = p1, *kp2 = p2;
> + int cmp1;
> + cmp1 = (kp1->ino > kp2->ino) - (kp1->ino < kp2->ino);
> + if (cmp1 != 0)
> + return cmp1;
> + return (kp1->dev > kp2->dev) - (kp1->dev < kp2->dev);
> +}
> +
> +
> +static int
> +add_object (struct ftw_data *data, struct STRUCT_STAT *st)
> +{
> + struct known_object *newp = malloc (sizeof (struct known_object));
> + if (newp == NULL)
> + return -1;
> + newp->dev = st->st_dev;
> + newp->ino = st->st_ino;
> + return __tsearch (newp, &data->known_objects, object_compare) ? 0 : -1;
> +}
> +
> +
> +static inline int
> +find_object (struct ftw_data *data, 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;
> +}
> +
> +
> +static inline int
> +open_dir_stream (int *dfdp, struct ftw_data *data, struct dir_data *dirp)
> +{
> + 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. */
> + free (buf);
> + 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)
> + {
> + free (buf);
> + 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;
> +}
> +
> +
> +static int
> +process_entry (struct ftw_data *data, struct dir_data *dir, const char *name,
> + size_t namlen, struct STRUCT_STAT *out_st, bool *descend)
> +{
> + struct STRUCT_STAT st;
> + int result = 0;
> + int flag = 0;
> + size_t new_buflen;
> +
> + *descend = false;
> +
> + 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))
> + {
> + *out_st = st;
> + *descend = true;
> + }
> + }
> + else
> + result = CALL_FUNC (data, data->dirbuf, &st, data->cvt_arr[flag],
> + &data->ftw);
> + }
> +
> + if ((data->flags & FTW_ACTIONRETVAL) && result == FTW_SKIP_SUBTREE)
> + result = 0;
> +
> + return result;
> +}
> +
> +
> +/* The ftw_frame are kept as chunked array to minimize the reallocation cost
> + when the stack grows (since it contains STRUCT_STAT and extra metadata).
> + New chunks of ftw_framw are allocated and only freed when ftw returns. */
> +enum
> +{
> + FTW_STACK_CHUNK_BLOCKS = 1, /* Number of initial allocated chunks. */
> + FTW_STACK_CHUNK_SIZE = 32 /* Number of stack frames allocated per
> + chunk. */
> +};
> +
> +static inline struct ftw_frame *
> +frame_stack_get (struct ftw_stack *ftwst, int adj)
> +{
> + return &ftwst->stack[(ftwst->top + adj) / FTW_STACK_CHUNK_SIZE]
> + [(ftwst->top + adj) % FTW_STACK_CHUNK_SIZE];
> +}
> +
> +static inline void
> +frame_stack_reset_top (struct ftw_stack *fwtst, const struct STRUCT_STAT *st)
> +{
> + struct ftw_frame *frame = frame_stack_get (fwtst, 0);
> + frame->st = *st;
> + frame->state = FTW_STATE_INIT;
> + frame->dir.stream = NULL;
> + frame->dir.content = NULL;
> + frame->dir.streamfd = -1;
> +}
> +
> +static bool
> +frame_stack_init (struct ftw_stack *ftwst, const struct STRUCT_STAT *st)
> +{
> + ftwst->num_blocks = FTW_STACK_CHUNK_BLOCKS;
> + ftwst->stack = malloc (FTW_STACK_CHUNK_BLOCKS * sizeof (*ftwst->stack));
> + if (ftwst->stack == NULL)
> + return false;
> +
> + ftwst->stack[0] = malloc (FTW_STACK_CHUNK_SIZE * sizeof (struct ftw_frame));
> + if (ftwst->stack[0] == NULL)
> + {
> + free (ftwst->stack);
> + return false;
> + }
> +
> + ftwst->top = 0;
> + frame_stack_reset_top (ftwst, st);
> + return true;
> +}
> +
> +static void
> +frame_stack_free (struct ftw_stack *ftwst)
> +{
> + for (size_t i = 0; i < ftwst->num_blocks; i++)
> + free (ftwst->stack[i]);
> + free (ftwst->stack);
> +}
> +
> +static bool
> +frame_stack_add (struct ftw_stack *ftwst, const struct STRUCT_STAT *st)
> +{
> + if (ftwst->top + 1 >= ftwst->num_blocks * FTW_STACK_CHUNK_SIZE)
> + {
> + size_t new_blocks = ftwst->num_blocks + 1;
> + struct ftw_frame **new_stack = realloc (
> + ftwst->stack, new_blocks * sizeof (*ftwst->stack));
> +
> + if (new_stack == NULL)
> + return false;
> + ftwst->stack = new_stack;
> + ftwst->stack[ftwst->num_blocks] = malloc (
> + FTW_STACK_CHUNK_SIZE * sizeof (struct ftw_frame));
> + if (ftwst->stack[ftwst->num_blocks] == NULL)
> + return false;
> + ftwst->num_blocks = new_blocks;
> + }
> + ftwst->top++;
> + frame_stack_reset_top (ftwst, st);
> + return true;
> +}
> +
> +static void
> +frame_closedir (struct ftw_data *data, struct ftw_frame *frame)
> +{
> + int save_err = errno;
> + assert (frame->dir.content == NULL);
> + __closedir (frame->dir.stream);
> + frame->dir.streamfd = -1;
> + __set_errno (save_err);
> + if (data->actdir-- == 0)
> + data->actdir = data->maxdir - 1;
> + data->dirstreams[data->actdir] = NULL;
> + frame->dir.stream = NULL;
> +}
> +
> +static int
> +ftw_dir (struct ftw_data *data, const struct STRUCT_STAT *st)
> +{
> + struct ftw_stack ftwst;
> + if (!frame_stack_init (&ftwst, st))
> + return -1;
> +
> + int result = 0;
> +
> + while (ftwst.top >= 0)
> + {
> + struct ftw_frame *frame = frame_stack_get (&ftwst, 0);
> + struct dir_data *old_dir = (ftwst.top > 0)
> + ? &frame_stack_get (&ftwst, -1)->dir : NULL;
> +
> + if (frame->state == FTW_STATE_INIT)
> + {
> + frame->previous_base = data->ftw.base;
> + result = open_dir_stream (
> + old_dir == NULL ? NULL : &old_dir->streamfd, data, &frame->dir);
> + if (result != 0)
> + {
> + if (errno == EACCES)
> + result = CALL_FUNC (data, data->dirbuf, &frame->st, FTW_DNR,
> + &data->ftw);
> + ftwst.top--;
> + /* Intercept FTW_SKIP_SUBTREE when popping frame */
> + if (ftwst.top >= 0 && (data->flags & FTW_ACTIONRETVAL)
> + && result == FTW_SKIP_SUBTREE)
> + result = 0;
> + continue;
> + }
> +
> + if (!(data->flags & FTW_DEPTH))
> + {
> + result = CALL_FUNC (data, data->dirbuf, &frame->st, FTW_D,
> + &data->ftw);
> + if (result != 0)
> + goto state0_fail;
> + }
> +
> + if (data->flags & FTW_CHDIR)
> + {
> + if (__fchdir (__dirfd (frame->dir.stream)) < 0)
> + {
> + result = -1;
> + state0_fail:
> + frame_closedir (data, frame);
> + ftwst.top--;
> + /* Intercept FTW_SKIP_SUBTREE when popping frame. */
> + if (ftwst.top >= 0 && (data->flags & FTW_ACTIONRETVAL)
> + && result == FTW_SKIP_SUBTREE)
> + result = 0;
> + continue;
> + }
> + }
> +
> + ++data->ftw.level;
> + char *startp = strchr (data->dirbuf, '\0');
> + assert (startp != data->dirbuf);
> + if (startp[-1] != '/')
> + *startp++ = '/';
> + data->ftw.base = startp - data->dirbuf;
> +
> + frame->state = FTW_STATE_STREAM_LOOP;
> + frame->runp = frame->dir.content;
> + }
> + else if (frame->state == FTW_STATE_STREAM_LOOP)
> + {
> + if (result != 0)
> + {
> + frame->state = FTW_STATE_CLEANUP;
> + continue;
> + }
> +
> + if (frame->dir.stream == NULL)
> + {
> + frame->state = FTW_STATE_CONTENT_LOOP;
> + frame->runp = frame->dir.content;
> + continue;
> + }
> +
> + struct dirent64 *d = __readdir64 (frame->dir.stream);
> + if (d != NULL)
> + {
> + struct STRUCT_STAT child_st;
> + bool descend = false;
> + result = process_entry (data, &frame->dir, d->d_name, NAMLEN (d),
> + &child_st, &descend);
> +
> + if (result == 0 && descend)
> + {
> + if (!frame_stack_add (&ftwst, &child_st))
> + {
> + result = -1;
> + frame->state = FTW_STATE_CLEANUP;
> + }
> + continue;
> + }
> + else if (result != 0)
> + {
> + frame->state = FTW_STATE_CLEANUP;
> + continue;
> + }
> + }
> + else
> + frame->state = FTW_STATE_CLEANUP;
> + }
> + else if (frame->state == FTW_STATE_CONTENT_LOOP)
> + {
> + /* Check if we are safely positioned to process the starting path.
> + The 'result' variable here comes from one of two places:
> +
> + 1. Initialization: defaults to 0 at the top of ftw_startup. If
> + the FTW_CHDIR flag was NOT passed, it remains 0, meaning we
> + are good to go.
> +
> + 2. Directory Change: If FTW_CHDIR WAS passed, 'result' holds the
> + return value of the preceding __chdir() call (either moving
> + to "/" or the parsed base directory).
> +
> + If 'result' is 0, the setup succeeded (or wasn't needed) and we
> + can safely stat the initial object. Othewise, the chdir failed,
> + so we skip processing and fall through to the cleanup phase. */
> + if (result != 0)
> + {
> + frame->state = FTW_STATE_CLEANUP;
> + continue;
> + }
> +
> + if (frame->runp != NULL && *frame->runp != '\0')
> + {
> + char *endp = strchr (frame->runp, '\0');
> + struct STRUCT_STAT child_st;
> + bool descend = false;
> +
> + result = process_entry (data, &frame->dir, frame->runp,
> + endp - frame->runp, &child_st,
> + &descend);
> + frame->runp = endp + 1;
> +
> + if (result == 0 && descend)
> + {
> + if (!frame_stack_add (&ftwst, &child_st))
> + {
> + result = -1;
> + frame->state = FTW_STATE_CLEANUP;
> + }
> + continue;
> + }
> + else if (result != 0)
> + {
> + frame->state = FTW_STATE_CLEANUP;
> + continue;
> + }
> + }
> + else
> + frame->state = FTW_STATE_CLEANUP;
> + }
> + else if (frame->state == FTW_STATE_CLEANUP)
> + {
> + if (frame->dir.stream != NULL)
> + frame_closedir (data, frame);
> + else if (frame->dir.content != NULL)
> + {
> + free (frame->dir.content);
> + frame->dir.content = NULL;
> + }
> +
> + if ((data->flags & FTW_ACTIONRETVAL) && result == FTW_SKIP_SIBLINGS)
> + result = 0;
> +
> + data->dirbuf[data->ftw.base - 1] = '\0';
> + --data->ftw.level;
> + data->ftw.base = frame->previous_base;
> +
> + if (result == 0 && (data->flags & FTW_DEPTH))
> + result
> + = CALL_FUNC (data, data->dirbuf, &frame->st, FTW_DP,
> + &data->ftw);
> +
> + if (old_dir != NULL && (data->flags & FTW_CHDIR)
> + && (result == 0
> + || ((data->flags & FTW_ACTIONRETVAL)
> + && (result != -1 && result != FTW_STOP))))
> + {
> + 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;
> + }
> + }
> +
> + ftwst.top--;
> + /* Intercept FTW_SKIP_SUBTREE when popping frame. */
> + if (ftwst.top >= 0 && (data->flags & FTW_ACTIONRETVAL)
> + && result == FTW_SKIP_SUBTREE)
> + result = 0;
> + }
> + }
> +
> + frame_stack_free (&ftwst);
> +
> + return result;
> +}
> +
> +
> +static int
> +ftw_startup (const char *dir, bool is_nftw, func_callback_t func,
> + int descriptors, int flags)
> +{
> + struct ftw_data data = { .dirstreams = NULL };
> + struct STRUCT_STAT st;
> + int result = 0;
> + int cwdfd = -1;
> + char *cwd = NULL;
> + char *cp;
> +
> + /* First make sure the parameters are reasonable. */
> + if (dir[0] == '\0')
> + {
> + __set_errno (ENOENT);
> + 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)))
> + 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;
> +
> + /* Find basename. */
> + while (cp > data.dirbuf && cp[-1] != '/')
> + --cp;
> + data.ftw.base = cp - data.dirbuf;
> +
> + data.flags = flags;
> +
> + data.is_nftw = is_nftw;
> + data.func = func;
> +
> + /* 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)
> + {
> + /* 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;
> + }
> + }
> + }
> +
> + /* Get stat info for start directory. */
> + if (result == 0)
> + {
> + const char *name;
> +
> + if (data.flags & FTW_CHDIR)
> + {
> + name = data.dirbuf + data.ftw.base;
> + if (name[0] == '\0')
> + name = ".";
> + }
> + else
> + name = data.dirbuf;
> +
> + if (((flags & FTW_PHYS)
> + ? LSTAT (name, &st)
> + : STAT (name, &st)) < 0)
> + {
> + if (!(flags & FTW_PHYS)
> + && errno == ENOENT
> + && LSTAT (name, &st) == 0
> + && S_ISLNK (st.st_mode))
> + result = CALL_FUNC (&data, 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))
> + {
> + /* Remember the device of the initial directory in case
> + FTW_MOUNT is given. */
> + data.dev = st.st_dev;
> +
> + /* We know this directory now. */
> + if (!(flags & FTW_PHYS))
> + result = add_object (&data, &st);
> +
> + if (result == 0)
> + result = ftw_dir (&data, &st);
> + }
> + else
> + {
> + int flag = S_ISLNK (st.st_mode) ? FTW_SL : FTW_F;
> +
> + result = CALL_FUNC (&data, data.dirbuf, &st, data.cvt_arr[flag],
> + &data.ftw);
> + }
> + }
> +
> + if ((flags & FTW_ACTIONRETVAL)
> + && (result == FTW_SKIP_SUBTREE || result == FTW_SKIP_SIBLINGS))
> + result = 0;
> + }
> +
> + /* 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:
> + __tdestroy (data.known_objects, free);
> + free (data.dirstreams);
> +
> + return result;
> +}
> +
> +
> +
> +/* Entry points. */
> +
> +int
> +FTW_NAME (const char *path, FTW_FUNC_T func, int descriptors)
> +{
> + return ftw_startup (path, false, (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, true, (func_callback_t) { .nftw_func = func },
> + descriptors, flags);
> +}
> +#else
> +
> +# include <shlib-compat.h>
> +
> +int NFTW_NEW_NAME (const char *, NFTW_FUNC_T, int, int);
> +
> +int
> +NFTW_NEW_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags)
> +{
> + if (flags
> + & ~(FTW_PHYS | FTW_MOUNT | FTW_CHDIR | FTW_DEPTH | FTW_ACTIONRETVAL))
> + {
> + __set_errno (EINVAL);
> + return -1;
> + }
> + return ftw_startup (path, true, (func_callback_t) { .nftw_func = func },
> + descriptors, flags);
> +}
> +
> +# if SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_3_3)
> +
> +/* Older nftw* version just ignored all unknown flags. */
> +
> +int NFTW_OLD_NAME (const char *, NFTW_FUNC_T, int, int);
> +
> +int
> +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, true, (func_callback_t) { .nftw_func = func },
> + descriptors, flags);
> +}
> +
> +# endif
> +#endif /* NFTW_OLD_NAME */
> diff --git a/io/ftw.c b/io/ftw.c
> index 726c430eaf..ed0eeb3904 100644
> --- a/io/ftw.c
> +++ b/io/ftw.c
> @@ -1,5 +1,5 @@
> -/* File tree walker functions.
> - Copyright (C) 1996-2026 Free Software Foundation, Inc.
> +/* File tree traversal functions LFS version.
> + 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
> @@ -16,956 +16,14 @@
> License along with the GNU C Library; if not, see
> <https://www.gnu.org/licenses/>. */
>
> +#include <sys/types.h>
>
> -#include <assert.h>
> -#include <dirent.h>
> -#include <fcntl.h>
> -#include <ftw.h>
> -#include <not-cancel.h>
> -#include <search.h>
> -#include <unistd.h>
> -#include <sys/param.h>
> +#ifndef __OFF_T_MATCHES_OFF64_T
> +# include "ftw-common.c"
>
> -#define NAMLEN(dirent) _D_EXACT_NAMLEN (dirent)
> -
> -/* Support for the LFS API version. */
> -#ifndef FTW_NAME
> -# define FTW_NAME ftw
> -# define NFTW_NAME nftw
> -# define NFTW_OLD_NAME __old_nftw
> -# define NFTW_NEW_NAME __new_nftw
> -# define INO_T ino_t
> -# define STRUCT_STAT stat
> -# define LSTAT __lstat
> -# define STAT __stat
> -# define FSTATAT __fstatat
> -# define FTW_FUNC_T __ftw_func_t
> -# define NFTW_FUNC_T __nftw_func_t
> +versioned_symbol (libc, __new_nftw, nftw, GLIBC_2_3_3);
> +#if SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_3_3)
> +compat_symbol (libc, __old_nftw, nftw, GLIBC_2_1);
> #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
> #endif
> -
> -struct dir_data
> -{
> - DIR *stream;
> - int streamfd;
> - char *content;
> -};
> -
> -struct known_object
> -{
> - dev_t dev;
> - INO_T ino;
> -};
> -
> -/* Represents the execution state of a directory processing frame within the
> - iterative file tree walk loop.
> -
> - Because the tree traversal is implemented iteratively using a custom stack
> - rather than standard recursion, this state machine tracks the progress
> - of each directory currently being visited. */
> -enum ftw_frame_state
> -{
> - /* The initial state of a newly pushed directory frame. Attempts to open
> - the directory stream. If successful, transitions to
> - FTW_STATE_STREAM_LOOP. */
> - FTW_STATE_INIT = 0,
> -
> - /* Iterating over the directory entries directly from the open DIR stream
> - (using readdir). If a subdirectory is encountered and needs to be
> - descended into, a new frame is added to the stack and execution pauses
> - here. Transitions to FTW_STATE_CONTENT_LOOP if the stream was closed
> - and cached to free up file descriptors, or FTW_STATE_CLEANUP when
> - done. */
> - FTW_STATE_STREAM_LOOP,
> -
> - /* Iterating over directory entries from a cached memory buffer. This state
> - is used as a fallback when the original DIR stream had to be closed
> - prematurely to prevent file descriptor exhaustion while descending into
> - deeply nested child directories. Transitions to FTW_STATE_CLEANUP when
> - all cached entries are processed. */
> - FTW_STATE_CONTENT_LOOP,
> -
> - /* The final state, handles resource deallocation (closing remaining
> - streams, freeing cached content buffers), triggering post-traversal
> - callbacks (like FTW_DP for FTW_DEPTH walks), and restoring the
> - previous working directory if FTW_CHDIR was used. */
> - FTW_STATE_CLEANUP
> -};
> -
> -/* Keep track of visited directories. */
> -struct ftw_frame
> -{
> - struct dir_data dir;
> - struct STRUCT_STAT st;
> - int previous_base;
> - char *runp;
> - enum ftw_frame_state state;
> -};
> -
> -struct ftw_stack
> -{
> - struct ftw_frame **stack;
> - size_t num_blocks;
> - ssize_t top;
> -};
> -
> -typedef union
> -{
> - NFTW_FUNC_T nftw_func;
> - FTW_FUNC_T ftw_func;
> -} func_callback_t;
> -
> -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. */
> - bool is_nftw;
> - func_callback_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;
> -};
> -#define CALL_FUNC(__ftw_data, __fp, __sb, __f, __ftw) \
> - ((__ftw_data)->is_nftw ? (__ftw_data)->func.nftw_func (__fp, __sb, __f, __ftw) \
> - : (__ftw_data)->func.ftw_func (__fp, __sb, __f))
> -
> -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
> -};
> -
> -
> -static int
> -object_compare (const void *p1, const void *p2)
> -{
> - /* We don't need a sophisticated and useful comparison. We are only
> - interested in equality. However, we must be careful not to
> - accidentally compare `holes' in the structure. */
> - const struct known_object *kp1 = p1, *kp2 = p2;
> - int cmp1;
> - cmp1 = (kp1->ino > kp2->ino) - (kp1->ino < kp2->ino);
> - if (cmp1 != 0)
> - return cmp1;
> - return (kp1->dev > kp2->dev) - (kp1->dev < kp2->dev);
> -}
> -
> -
> -static int
> -add_object (struct ftw_data *data, struct STRUCT_STAT *st)
> -{
> - struct known_object *newp = malloc (sizeof (struct known_object));
> - if (newp == NULL)
> - return -1;
> - newp->dev = st->st_dev;
> - newp->ino = st->st_ino;
> - return __tsearch (newp, &data->known_objects, object_compare) ? 0 : -1;
> -}
> -
> -
> -static inline int
> -find_object (struct ftw_data *data, 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;
> -}
> -
> -
> -static inline int
> -open_dir_stream (int *dfdp, struct ftw_data *data, struct dir_data *dirp)
> -{
> - 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. */
> - free (buf);
> - 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)
> - {
> - free (buf);
> - 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;
> -}
> -
> -
> -static int
> -process_entry (struct ftw_data *data, struct dir_data *dir, const char *name,
> - size_t namlen, struct STRUCT_STAT *out_st, bool *descend)
> -{
> - struct STRUCT_STAT st;
> - int result = 0;
> - int flag = 0;
> - size_t new_buflen;
> -
> - *descend = false;
> -
> - 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))
> - {
> - *out_st = st;
> - *descend = true;
> - }
> - }
> - else
> - result = CALL_FUNC (data, data->dirbuf, &st, data->cvt_arr[flag],
> - &data->ftw);
> - }
> -
> - if ((data->flags & FTW_ACTIONRETVAL) && result == FTW_SKIP_SUBTREE)
> - result = 0;
> -
> - return result;
> -}
> -
> -
> -/* The ftw_frame are kept as chunked array to minimize the reallocation cost
> - when the stack grows (since it contains STRUCT_STAT and extra metadata).
> - New chunks of ftw_framw are allocated and only freed when ftw returns. */
> -enum
> -{
> - FTW_STACK_CHUNK_BLOCKS = 1, /* Number of initial allocated chunks. */
> - FTW_STACK_CHUNK_SIZE = 32 /* Number of stack frames allocated per
> - chunk. */
> -};
> -
> -static inline struct ftw_frame *
> -frame_stack_get (struct ftw_stack *ftwst, int adj)
> -{
> - return &ftwst->stack[(ftwst->top + adj) / FTW_STACK_CHUNK_SIZE]
> - [(ftwst->top + adj) % FTW_STACK_CHUNK_SIZE];
> -}
> -
> -static inline void
> -frame_stack_reset_top (struct ftw_stack *fwtst, const struct STRUCT_STAT *st)
> -{
> - struct ftw_frame *frame = frame_stack_get (fwtst, 0);
> - frame->st = *st;
> - frame->state = FTW_STATE_INIT;
> - frame->dir.stream = NULL;
> - frame->dir.content = NULL;
> - frame->dir.streamfd = -1;
> -}
> -
> -static bool
> -frame_stack_init (struct ftw_stack *ftwst, const struct STRUCT_STAT *st)
> -{
> - ftwst->num_blocks = FTW_STACK_CHUNK_BLOCKS;
> - ftwst->stack = malloc (FTW_STACK_CHUNK_BLOCKS * sizeof (*ftwst->stack));
> - if (ftwst->stack == NULL)
> - return false;
> -
> - ftwst->stack[0] = malloc (FTW_STACK_CHUNK_SIZE * sizeof (struct ftw_frame));
> - if (ftwst->stack[0] == NULL)
> - {
> - free (ftwst->stack);
> - return false;
> - }
> -
> - ftwst->top = 0;
> - frame_stack_reset_top (ftwst, st);
> - return true;
> -}
> -
> -static void
> -frame_stack_free (struct ftw_stack *ftwst)
> -{
> - for (size_t i = 0; i < ftwst->num_blocks; i++)
> - free (ftwst->stack[i]);
> - free (ftwst->stack);
> -}
> -
> -static bool
> -frame_stack_add (struct ftw_stack *ftwst, const struct STRUCT_STAT *st)
> -{
> - if (ftwst->top + 1 >= ftwst->num_blocks * FTW_STACK_CHUNK_SIZE)
> - {
> - size_t new_blocks = ftwst->num_blocks + 1;
> - struct ftw_frame **new_stack = realloc (
> - ftwst->stack, new_blocks * sizeof (*ftwst->stack));
> -
> - if (new_stack == NULL)
> - return false;
> - ftwst->stack = new_stack;
> - ftwst->stack[ftwst->num_blocks] = malloc (
> - FTW_STACK_CHUNK_SIZE * sizeof (struct ftw_frame));
> - if (ftwst->stack[ftwst->num_blocks] == NULL)
> - return false;
> - ftwst->num_blocks = new_blocks;
> - }
> - ftwst->top++;
> - frame_stack_reset_top (ftwst, st);
> - return true;
> -}
> -
> -static void
> -frame_closedir (struct ftw_data *data, struct ftw_frame *frame)
> -{
> - int save_err = errno;
> - assert (frame->dir.content == NULL);
> - __closedir (frame->dir.stream);
> - frame->dir.streamfd = -1;
> - __set_errno (save_err);
> - if (data->actdir-- == 0)
> - data->actdir = data->maxdir - 1;
> - data->dirstreams[data->actdir] = NULL;
> - frame->dir.stream = NULL;
> -}
> -
> -static int
> -ftw_dir (struct ftw_data *data, const struct STRUCT_STAT *st)
> -{
> - struct ftw_stack ftwst;
> - if (!frame_stack_init (&ftwst, st))
> - return -1;
> -
> - int result = 0;
> -
> - while (ftwst.top >= 0)
> - {
> - struct ftw_frame *frame = frame_stack_get (&ftwst, 0);
> - struct dir_data *old_dir = (ftwst.top > 0)
> - ? &frame_stack_get (&ftwst, -1)->dir : NULL;
> -
> - if (frame->state == FTW_STATE_INIT)
> - {
> - frame->previous_base = data->ftw.base;
> - result = open_dir_stream (
> - old_dir == NULL ? NULL : &old_dir->streamfd, data, &frame->dir);
> - if (result != 0)
> - {
> - if (errno == EACCES)
> - result = CALL_FUNC (data, data->dirbuf, &frame->st, FTW_DNR,
> - &data->ftw);
> - ftwst.top--;
> - /* Intercept FTW_SKIP_SUBTREE when popping frame */
> - if (ftwst.top >= 0 && (data->flags & FTW_ACTIONRETVAL)
> - && result == FTW_SKIP_SUBTREE)
> - result = 0;
> - continue;
> - }
> -
> - if (!(data->flags & FTW_DEPTH))
> - {
> - result = CALL_FUNC (data, data->dirbuf, &frame->st, FTW_D,
> - &data->ftw);
> - if (result != 0)
> - goto state0_fail;
> - }
> -
> - if (data->flags & FTW_CHDIR)
> - {
> - if (__fchdir (__dirfd (frame->dir.stream)) < 0)
> - {
> - result = -1;
> - state0_fail:
> - frame_closedir (data, frame);
> - ftwst.top--;
> - /* Intercept FTW_SKIP_SUBTREE when popping frame. */
> - if (ftwst.top >= 0 && (data->flags & FTW_ACTIONRETVAL)
> - && result == FTW_SKIP_SUBTREE)
> - result = 0;
> - continue;
> - }
> - }
> -
> - ++data->ftw.level;
> - char *startp = strchr (data->dirbuf, '\0');
> - assert (startp != data->dirbuf);
> - if (startp[-1] != '/')
> - *startp++ = '/';
> - data->ftw.base = startp - data->dirbuf;
> -
> - frame->state = FTW_STATE_STREAM_LOOP;
> - frame->runp = frame->dir.content;
> - }
> - else if (frame->state == FTW_STATE_STREAM_LOOP)
> - {
> - if (result != 0)
> - {
> - frame->state = FTW_STATE_CLEANUP;
> - continue;
> - }
> -
> - if (frame->dir.stream == NULL)
> - {
> - frame->state = FTW_STATE_CONTENT_LOOP;
> - frame->runp = frame->dir.content;
> - continue;
> - }
> -
> - struct dirent64 *d = __readdir64 (frame->dir.stream);
> - if (d != NULL)
> - {
> - struct STRUCT_STAT child_st;
> - bool descend = false;
> - result = process_entry (data, &frame->dir, d->d_name, NAMLEN (d),
> - &child_st, &descend);
> -
> - if (result == 0 && descend)
> - {
> - if (!frame_stack_add (&ftwst, &child_st))
> - {
> - result = -1;
> - frame->state = FTW_STATE_CLEANUP;
> - }
> - continue;
> - }
> - else if (result != 0)
> - {
> - frame->state = FTW_STATE_CLEANUP;
> - continue;
> - }
> - }
> - else
> - frame->state = FTW_STATE_CLEANUP;
> - }
> - else if (frame->state == FTW_STATE_CONTENT_LOOP)
> - {
> - /* Check if we are safely positioned to process the starting path.
> - The 'result' variable here comes from one of two places:
> -
> - 1. Initialization: defaults to 0 at the top of ftw_startup. If
> - the FTW_CHDIR flag was NOT passed, it remains 0, meaning we
> - are good to go.
> -
> - 2. Directory Change: If FTW_CHDIR WAS passed, 'result' holds the
> - return value of the preceding __chdir() call (either moving
> - to "/" or the parsed base directory).
> -
> - If 'result' is 0, the setup succeeded (or wasn't needed) and we
> - can safely stat the initial object. Othewise, the chdir failed,
> - so we skip processing and fall through to the cleanup phase. */
> - if (result != 0)
> - {
> - frame->state = FTW_STATE_CLEANUP;
> - continue;
> - }
> -
> - if (frame->runp != NULL && *frame->runp != '\0')
> - {
> - char *endp = strchr (frame->runp, '\0');
> - struct STRUCT_STAT child_st;
> - bool descend = false;
> -
> - result = process_entry (data, &frame->dir, frame->runp,
> - endp - frame->runp, &child_st,
> - &descend);
> - frame->runp = endp + 1;
> -
> - if (result == 0 && descend)
> - {
> - if (!frame_stack_add (&ftwst, &child_st))
> - {
> - result = -1;
> - frame->state = FTW_STATE_CLEANUP;
> - }
> - continue;
> - }
> - else if (result != 0)
> - {
> - frame->state = FTW_STATE_CLEANUP;
> - continue;
> - }
> - }
> - else
> - frame->state = FTW_STATE_CLEANUP;
> - }
> - else if (frame->state == FTW_STATE_CLEANUP)
> - {
> - if (frame->dir.stream != NULL)
> - frame_closedir (data, frame);
> - else if (frame->dir.content != NULL)
> - {
> - free (frame->dir.content);
> - frame->dir.content = NULL;
> - }
> -
> - if ((data->flags & FTW_ACTIONRETVAL) && result == FTW_SKIP_SIBLINGS)
> - result = 0;
> -
> - data->dirbuf[data->ftw.base - 1] = '\0';
> - --data->ftw.level;
> - data->ftw.base = frame->previous_base;
> -
> - if (result == 0 && (data->flags & FTW_DEPTH))
> - result
> - = CALL_FUNC (data, data->dirbuf, &frame->st, FTW_DP,
> - &data->ftw);
> -
> - if (old_dir != NULL && (data->flags & FTW_CHDIR)
> - && (result == 0
> - || ((data->flags & FTW_ACTIONRETVAL)
> - && (result != -1 && result != FTW_STOP))))
> - {
> - 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;
> - }
> - }
> -
> - ftwst.top--;
> - /* Intercept FTW_SKIP_SUBTREE when popping frame. */
> - if (ftwst.top >= 0 && (data->flags & FTW_ACTIONRETVAL)
> - && result == FTW_SKIP_SUBTREE)
> - result = 0;
> - }
> - }
> -
> - frame_stack_free (&ftwst);
> -
> - return result;
> -}
> -
> -
> -static int
> -ftw_startup (const char *dir, bool is_nftw, func_callback_t func,
> - int descriptors, int flags)
> -{
> - struct ftw_data data = { .dirstreams = NULL };
> - struct STRUCT_STAT st;
> - int result = 0;
> - int cwdfd = -1;
> - char *cwd = NULL;
> - char *cp;
> -
> - /* First make sure the parameters are reasonable. */
> - if (dir[0] == '\0')
> - {
> - __set_errno (ENOENT);
> - 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)))
> - 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;
> -
> - /* Find basename. */
> - while (cp > data.dirbuf && cp[-1] != '/')
> - --cp;
> - data.ftw.base = cp - data.dirbuf;
> -
> - data.flags = flags;
> -
> - data.is_nftw = is_nftw;
> - data.func = func;
> -
> - /* 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)
> - {
> - /* 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;
> - }
> - }
> - }
> -
> - /* Get stat info for start directory. */
> - if (result == 0)
> - {
> - const char *name;
> -
> - if (data.flags & FTW_CHDIR)
> - {
> - name = data.dirbuf + data.ftw.base;
> - if (name[0] == '\0')
> - name = ".";
> - }
> - else
> - name = data.dirbuf;
> -
> - if (((flags & FTW_PHYS)
> - ? LSTAT (name, &st)
> - : STAT (name, &st)) < 0)
> - {
> - if (!(flags & FTW_PHYS)
> - && errno == ENOENT
> - && LSTAT (name, &st) == 0
> - && S_ISLNK (st.st_mode))
> - result = CALL_FUNC (&data, 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))
> - {
> - /* Remember the device of the initial directory in case
> - FTW_MOUNT is given. */
> - data.dev = st.st_dev;
> -
> - /* We know this directory now. */
> - if (!(flags & FTW_PHYS))
> - result = add_object (&data, &st);
> -
> - if (result == 0)
> - result = ftw_dir (&data, &st);
> - }
> - else
> - {
> - int flag = S_ISLNK (st.st_mode) ? FTW_SL : FTW_F;
> -
> - result = CALL_FUNC (&data, data.dirbuf, &st, data.cvt_arr[flag],
> - &data.ftw);
> - }
> - }
> -
> - if ((flags & FTW_ACTIONRETVAL)
> - && (result == FTW_SKIP_SUBTREE || result == FTW_SKIP_SIBLINGS))
> - result = 0;
> - }
> -
> - /* 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:
> - __tdestroy (data.known_objects, free);
> - free (data.dirstreams);
> -
> - return result;
> -}
> -
> -
> -
> -/* Entry points. */
> -
> -int
> -FTW_NAME (const char *path, FTW_FUNC_T func, int descriptors)
> -{
> - return ftw_startup (path, false, (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, true, (func_callback_t) { .nftw_func = func },
> - descriptors, flags);
> -}
> -#else
> -
> -# include <shlib-compat.h>
> -
> -int NFTW_NEW_NAME (const char *, NFTW_FUNC_T, int, int);
> -
> -int
> -NFTW_NEW_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags)
> -{
> - if (flags
> - & ~(FTW_PHYS | FTW_MOUNT | FTW_CHDIR | FTW_DEPTH | FTW_ACTIONRETVAL))
> - {
> - __set_errno (EINVAL);
> - return -1;
> - }
> - return ftw_startup (path, true, (func_callback_t) { .nftw_func = func },
> - descriptors, flags);
> -}
> -versioned_symbol (libc, NFTW_NEW_NAME, NFTW_NAME, GLIBC_2_3_3);
> -
> -# if SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_3_3)
> -
> -/* Older nftw* version just ignored all unknown flags. */
> -
> -int NFTW_OLD_NAME (const char *, NFTW_FUNC_T, int, int);
> -
> -int
> -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, true, (func_callback_t) { .nftw_func = func },
> - descriptors, flags);
> -}
> -
> -compat_symbol (libc, NFTW_OLD_NAME, NFTW_NAME, GLIBC_2_1);
> -# endif
> -#endif /* NFTW_OLD_NAME */
> diff --git a/io/ftw64-time64.c b/io/ftw64-time64.c
> index 2df871f802..88f58fd85c 100644
> --- a/io/ftw64-time64.c
> +++ b/io/ftw64-time64.c
> @@ -29,5 +29,5 @@
> # define FTW_FUNC_T __ftw64_time64_func_t
> # define NFTW_FUNC_T __nftw64_time64_func_t
>
> -# include "ftw.c"
> +# include "ftw-common.c"
> #endif
> diff --git a/io/ftw64.c b/io/ftw64.c
> index 0d7cb30091..d3cd14c21a 100644
> --- a/io/ftw64.c
> +++ b/io/ftw64.c
> @@ -16,8 +16,8 @@
> License along with the GNU C Library; if not, see
> <https://www.gnu.org/licenses/>. */
>
> -#define FTW_NAME ftw64
> -#define NFTW_NAME nftw64
> +#define FTW_NAME __ftw64
> +#define NFTW_NAME __nftw64
> #define NFTW_OLD_NAME __old_nftw64
> #define NFTW_NEW_NAME __new_nftw64
> #define INO_T ino64_t
> @@ -28,4 +28,26 @@
> #define FTW_FUNC_T __ftw64_func_t
> #define NFTW_FUNC_T __nftw64_func_t
>
> -#include "ftw.c"
> +#define ftw __rename_ftw
> +#define nftw __rename_nftw
> +
> +#include <shlib-compat.h>
> +#include "ftw-common.c"
> +
> +#undef ftw
> +#undef nftw
> +
> +weak_alias (__ftw64, ftw64)
> +versioned_symbol (libc, __new_nftw64, nftw64, GLIBC_2_3_3);
> +
> +#if SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_3_3)
> +compat_symbol (libc, __old_nftw64, nftw64, GLIBC_2_1);
> +#endif
> +
> +#ifdef __OFF_T_MATCHES_OFF64_T
> +weak_alias (__ftw64, ftw)
> +versioned_symbol (libc, __new_nftw64, nftw, GLIBC_2_3_3);
> +# if SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_3_3)
> +compat_symbol (libc, __old_nftw64, nftw, GLIBC_2_1);
> +# endif
> +#endif
> diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw.c b/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw.c
> deleted file mode 100644
> index 46389568b2..0000000000
> --- a/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw.c
> +++ /dev/null
> @@ -1 +0,0 @@
> -#include <io/ftw.c>
> diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw64.c b/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw64.c
> deleted file mode 100644
> index cb02172b3e..0000000000
> --- a/sysdeps/unix/sysv/linux/mips/mips64/n64/ftw64.c
> +++ /dev/null
> @@ -1 +0,0 @@
> -#include <io/ftw64.c>
> diff --git a/sysdeps/unix/sysv/linux/x86_64/x32/ftw.c b/sysdeps/unix/sysv/linux/x86_64/x32/ftw.c
> deleted file mode 100644
> index a21dfe5690..0000000000
> --- a/sysdeps/unix/sysv/linux/x86_64/x32/ftw.c
> +++ /dev/null
> @@ -1 +0,0 @@
> -#include <sysdeps/wordsize-64/ftw.c>
> diff --git a/sysdeps/unix/sysv/linux/x86_64/x32/ftw64.c b/sysdeps/unix/sysv/linux/x86_64/x32/ftw64.c
> deleted file mode 100644
> index 3c025b738a..0000000000
> --- a/sysdeps/unix/sysv/linux/x86_64/x32/ftw64.c
> +++ /dev/null
> @@ -1 +0,0 @@
> -#include <sysdeps/wordsize-64/ftw64.c>
> diff --git a/sysdeps/wordsize-64/ftw.c b/sysdeps/wordsize-64/ftw.c
> deleted file mode 100644
> index ca19903799..0000000000
> --- a/sysdeps/wordsize-64/ftw.c
> +++ /dev/null
> @@ -1,16 +0,0 @@
> -#define ftw64 __rename_ftw64
> -#define nftw64 __rename_nftw64
> -
> -#include "../../io/ftw.c"
> -
> -#undef ftw64
> -#undef nftw64
> -
> -weak_alias (ftw, ftw64)
> -strong_alias (__new_nftw, __new_nftw64)
> -versioned_symbol (libc, __new_nftw64, nftw64, GLIBC_2_3_3);
> -
> -#if SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_3_3)
> -strong_alias (__old_nftw, __old_nftw64)
> -compat_symbol (libc, __old_nftw64, nftw64, GLIBC_2_1);
> -#endif
> diff --git a/sysdeps/wordsize-64/ftw64.c b/sysdeps/wordsize-64/ftw64.c
> deleted file mode 100644
> index 1cfcaadfd1..0000000000
> --- a/sysdeps/wordsize-64/ftw64.c
> +++ /dev/null
> @@ -1 +0,0 @@
> -/* Defined in ftw.c. */
More information about the Libc-alpha
mailing list