[PATCH 2/3] io: Consolidate ftw implementation
Adhemerval Zanella
adhemerval.zanella@linaro.org
Fri Apr 17 13:24:56 GMT 2026
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. */
--
2.43.0
More information about the Libc-alpha
mailing list