[glibc] io: Consolidate ftw implementation
Adhemerval Zanella via Glibc-cvs <[email protected]>
| Newsgroups | gmane.comp.lib.glibc.cvs |
|---|---|
| Message-ID | <[email protected]> |
https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=ed1eed7da7a0d0c9cf01c9c387f55ac0da16d285 commit ed1eed7da7a0d0c9cf01c9c387f55ac0da16d285 Author: Adhemerval Zanella <[email protected]> Date: Thu Apr 16 14:28:49 2026 -0300 io: Consolidate ftw implementation 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. Diff: --- 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 - sysdeps/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(-) 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. */