[PATCH 1/3] io: Consolidate fts implementation
Adhemerval Zanella Netto
adhemerval.zanella@linaro.org
Fri Apr 17 17:31:13 GMT 2026
This is a mechanical patch and I install if no one opposes it.
On 17/04/26 10:24, Adhemerval Zanella wrote:
> Remove wordsize-64 and arch-specific implementations, for ABIs when
> off_t is the same as off64_t (__OFF_T_MATCHES_OFF64_T) the fts64.c
> will create the requires aliases.
>
> The fts.c implementation is moved to fts-common.c to simplify
> the __OFF_T_MATCHES_OFF64_T usage.
> ---
> SHARED-FILES | 2 +-
> io/fts-common.c | 2208 ++++++++++++++++
> io/fts.c | 2217 +----------------
> io/fts64-time64.c | 2 +-
> io/fts64.c | 38 +-
> sysdeps/unix/sysv/linux/mips/mips64/n64/fts.c | 1 -
> .../unix/sysv/linux/mips/mips64/n64/fts64.c | 1 -
> sysdeps/unix/sysv/linux/x86_64/x32/fts.c | 1 -
> sysdeps/unix/sysv/linux/x86_64/x32/fts64.c | 1 -
> sysdeps/wordsize-64/fts.c | 19 -
> sysdeps/wordsize-64/fts64.c | 1 -
> 11 files changed, 2258 insertions(+), 2233 deletions(-)
> create mode 100644 io/fts-common.c
> delete mode 100644 sysdeps/unix/sysv/linux/mips/mips64/n64/fts.c
> delete mode 100644 sysdeps/unix/sysv/linux/mips/mips64/n64/fts64.c
> delete mode 100644 sysdeps/unix/sysv/linux/x86_64/x32/fts.c
> delete mode 100644 sysdeps/unix/sysv/linux/x86_64/x32/fts64.c
> delete mode 100644 sysdeps/wordsize-64/fts.c
> delete mode 100644 sysdeps/wordsize-64/fts64.c
>
> diff --git a/SHARED-FILES b/SHARED-FILES
> index 126aaf096d..645040d7e1 100644
> --- a/SHARED-FILES
> +++ b/SHARED-FILES
> @@ -49,7 +49,7 @@ gnulib:
> io/cycle-check.h
> io/dev-ino.h
> io/fts-cycle.c
> - io/fts.c
> + io/fts-common.c
> io/i-ring.c
> io/same-inode.h
> locale/programs/3level.h
> diff --git a/io/fts-common.c b/io/fts-common.c
> new file mode 100644
> index 0000000000..3825f0aeb4
> --- /dev/null
> +++ b/io/fts-common.c
> @@ -0,0 +1,2208 @@
> +/* Traverse a file hierarchy.
> +
> + Copyright (C) 2004-2026 Free Software Foundation, Inc.
> +
> + This file 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.
> +
> + This file 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 this program. If not, see <https://www.gnu.org/licenses/>. */
> +
> +/*-
> + * Copyright (c) 1990, 1993, 1994
> + * The Regents of the University of California. All rights reserved.
> + *
> + * Redistribution and use in source and binary forms, with or without
> + * modification, are permitted provided that the following conditions
> + * are met:
> + * 1. Redistributions of source code must retain the above copyright
> + * notice, this list of conditions and the following disclaimer.
> + * 2. Redistributions in binary form must reproduce the above copyright
> + * notice, this list of conditions and the following disclaimer in the
> + * documentation and/or other materials provided with the distribution.
> + * 4. Neither the name of the University nor the names of its contributors
> + * may be used to endorse or promote products derived from this software
> + * without specific prior written permission.
> + *
> + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
> + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
> + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
> + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
> + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
> + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
> + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
> + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
> + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
> + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
> + * SUCH DAMAGE.
> + */
> +
> +#include <config.h>
> +
> +#if defined LIBC_SCCS && !defined GCC_LINT && !defined lint
> +static char sccsid[] = "@(#)fts.c 8.6 (Berkeley) 8/14/94";
> +#endif
> +
> +#if _LIBC
> +# include <fts.h>
> +#else
> +# include "fts_.h"
> +#endif
> +#if _LIBC || HAVE_SYS_PARAM_H
> +# include <sys/param.h>
> +#endif
> +#include <sys/stat.h>
> +#include <fcntl.h>
> +#include <errno.h>
> +#include <stddef.h>
> +#include <stdint.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +
> +/* Support for the LFS API version. */
> +#ifndef FTS_OPEN
> +# define FTS_OPEN fts_open
> +# define FTS_CLOSE fts_close
> +# define FTS_READ fts_read
> +# define FTS_SET fts_set
> +# define FTS_CHILDREN fts_children
> +# define FTSOBJ FTS
> +# define FTSENTRY FTSENT
> +# define INO_T ino_t
> +# define STRUCT_STAT stat
> +# define FSTAT __fstat
> +# define FSTATAT __fstatat
> +# define STRUCT_STATFS statfs
> +# define FSTATFS __fstatfs
> +#endif
> +
> +#if ! _LIBC
> +# include "attribute.h"
> +# include "fcntl--.h"
> +# include "openat.h"
> +# include "opendirat.h"
> +# include "same-inode.h"
> +# define OPENDIRAT opendirat
> +# define FTSENT_WRAPPER(__p) __p
> +# define FTS_COMPAR_CAST(__fn) __fn
> +#else
> +# include <stdbool.h>
> +
> +# define internal_function
> +# define FALLTHROUGH ; [[fallthrough]]
> +# define HAVE_STRUCT_DIRENT_D_TYPE 1
> +# define GNULIB_FTS_DEBUG 0
> +# ifdef O_PATH
> +# define O_SEARCH O_PATH
> +# else
> +# define O_SEARCH O_RDONLY
> +# endif
> +# define HAVE_SYS_VFS_H 1
> +# define HAVE_FSTATFS 1
> +# define HAVE_STRUCT_STATFS_F_TYPE 1
> +# define HAVE_OPENAT 1
> +# define HAVE_WORKING_O_NOFOLLOW 1
> +# define _GL_CMP(a, b) ((a) < (b) ? -1 : (a) > (b))
> +# define OPENDIRAT __opendirat
> +
> +static inline bool openat_needs_fchdir (void)
> +{
> + return false;
> +}
> +
> +static inline bool streq (const char *s1, const char *s2)
> +{
> + return strcmp (s1, s2) == 0;
> +}
> +# define reallocarray __libc_reallocarray
> +# define fchdir __fchdir
> +# define close __close
> +# define closedir __closedir
> +# define fcntl __fcntl
> +# define readdir __readdir
> +# ifndef dirfd
> +# define dirfd __dirfd
> +# endif
> +# define open __open
> +# define openat __openat
> +
> +# include "cycle-check.c"
> +# include "i-ring.c"
> +
> +struct FTSENT_wrapper
> +{
> + FTSOBJ *fts_fts; /* the file hierarchy itself */
> + DIR *fts_dirp; /* Dir pointer for any directory containing
> + more entries than we read at one time. */
> + struct STRUCT_STAT fts_stat;
> +
> + FTSENTRY ent;
> +};
> +
> +/* glibc historicaly defines the FTS::fts_compar as having 'void *', while the
> + fts_open has a function point using 'FTSENT **' as argument. */
> +# define FTS_COMPAR_CAST(__fn) ((int (*) (void const *, void const *))__fn)
> +
> +# define FTSENT_WRAPPER(p) \
> + ((struct FTSENT_wrapper *) ((char *)(p) - offsetof(struct FTSENT_wrapper, ent)))
> +#endif
> +#define FTSENT_FTS(p) (FTSENT_WRAPPER(p)->fts_fts)
> +#define FTSENT_DIRP(p) (FTSENT_WRAPPER(p)->fts_dirp)
> +
> +#include "flexmember.h"
> +
> +#include <dirent.h>
> +#ifndef _D_EXACT_NAMLEN
> +# define _D_EXACT_NAMLEN(dirent) strlen ((dirent)->d_name)
> +#endif
> +
> +#if HAVE_STRUCT_DIRENT_D_TYPE
> +/* True if the type of the directory entry D is known. */
> +# define DT_IS_KNOWN(d) ((d)->d_type != DT_UNKNOWN)
> +/* True if the type of the directory entry D must be T. */
> +# define DT_MUST_BE(d, t) ((d)->d_type == (t))
> +# define D_TYPE(d) ((d)->d_type)
> +#else
> +# define DT_IS_KNOWN(d) false
> +# define DT_MUST_BE(d, t) false
> +# define D_TYPE(d) DT_UNKNOWN
> +
> +# undef DT_UNKNOWN
> +# define DT_UNKNOWN 0
> +
> +/* Any nonzero values will do here, so long as they're distinct.
> + Undef any existing macros out of the way. */
> +# undef DT_BLK
> +# undef DT_CHR
> +# undef DT_DIR
> +# undef DT_FIFO
> +# undef DT_LNK
> +# undef DT_REG
> +# undef DT_SOCK
> +# define DT_BLK 1
> +# define DT_CHR 2
> +# define DT_DIR 3
> +# define DT_FIFO 4
> +# define DT_LNK 5
> +# define DT_REG 6
> +# define DT_SOCK 7
> +#endif
> +
> +#ifndef S_IFBLK
> +# define S_IFBLK 0
> +#endif
> +#ifndef S_IFLNK
> +# define S_IFLNK 0
> +#endif
> +#ifndef S_IFSOCK
> +# define S_IFSOCK 0
> +#endif
> +
> +enum
> +{
> + NOT_AN_INODE_NUMBER = 0
> +};
> +
> +#ifdef D_INO_IN_DIRENT
> +# define D_INO(dp) (dp)->d_ino
> +#else
> +/* Some systems don't have inodes, so fake them to avoid lots of ifdefs. */
> +# define D_INO(dp) NOT_AN_INODE_NUMBER
> +#endif
> +
> +/* If possible (see max_entries, below), read no more than this many directory
> + entries at a time. Without this limit (i.e., when using non-NULL
> + fts_compar), processing a directory with 4,000,000 entries requires ~1GiB
> + of memory, and handling 64M entries would require 16GiB of memory. */
> +#ifndef FTS_MAX_READDIR_ENTRIES
> +# define FTS_MAX_READDIR_ENTRIES 100000
> +#endif
> +
> +/* If there are more than this many entries in a directory,
> + and the conditions mentioned below are satisfied, then sort
> + the entries on inode number before any further processing. */
> +#ifndef FTS_INODE_SORT_DIR_ENTRIES_THRESHOLD
> +# define FTS_INODE_SORT_DIR_ENTRIES_THRESHOLD 10000
> +#endif
> +
> +enum
> +{
> + _FTS_INODE_SORT_DIR_ENTRIES_THRESHOLD = FTS_INODE_SORT_DIR_ENTRIES_THRESHOLD
> +};
> +
> +enum Fts_stat
> +{
> + FTS_NO_STAT_REQUIRED = 1,
> + FTS_STAT_REQUIRED = 2
> +};
> +
> +#ifndef __set_errno
> +# define __set_errno(Val) errno = (Val)
> +#endif
> +
> +/* If this host provides the openat function, then we can avoid
> + attempting to open "." in some initialization code below. */
> +#ifdef HAVE_OPENAT
> +# define HAVE_OPENAT_SUPPORT 1
> +#else
> +# define HAVE_OPENAT_SUPPORT 0
> +#endif
> +
> +#ifdef NDEBUG
> +# define fts_assert(expr) ((void) (0 && (expr)))
> +#else
> +# define fts_assert(expr) \
> + do \
> + { \
> + if (!(expr)) \
> + abort (); \
> + } \
> + while (false)
> +#endif
> +
> +static FTSENTRY *fts_alloc (FTSOBJ *, const char *, size_t);
> +static FTSENTRY *fts_build (FTSOBJ *, int);
> +static void fts_lfree (FTSENTRY *);
> +static void fts_load (FTSOBJ *, FTSENTRY *);
> +static size_t fts_maxarglen (char * const *);
> +static void fts_padjust (FTSOBJ *, FTSENTRY *);
> +static bool fts_palloc (FTSOBJ *, size_t);
> +static FTSENTRY *fts_sort (FTSOBJ *, FTSENTRY *, size_t);
> +static unsigned short int fts_stat (FTSOBJ *, FTSENTRY *, bool);
> +static int fts_safe_changedir (FTSOBJ *, FTSENTRY *, int, const char *);
> +
> +#include "fts-cycle.c"
> +
> +#ifndef MAX
> +# define MAX(a,b) ((a) > (b) ? (a) : (b))
> +#endif
> +
> +#ifndef SIZE_MAX
> +# define SIZE_MAX ((size_t) -1)
> +#endif
> +
> +#define ISDOT(a) (a[0] == '.' && (!a[1] || (a[1] == '.' && !a[2])))
> +
> +#define CLR(opt) (sp->fts_options &= ~(opt))
> +#define ISSET(opt) ((sp->fts_options & (opt)) != 0)
> +#define SET(opt) (sp->fts_options |= (opt))
> +
> +/* FIXME: FTS_NOCHDIR is now misnamed.
> + Call it FTS_USE_FULL_RELATIVE_FILE_NAMES instead. */
> +#define FCHDIR(sp, fd) \
> + (!ISSET(FTS_NOCHDIR) && (ISSET(FTS_CWDFD) \
> + ? (cwd_advance_fd ((sp), (fd), true), 0) \
> + : fchdir (fd)))
> +
> +
> +/* fts_build flags */
> +/* FIXME: make this an enum */
> +#define BCHILD 1 /* fts_children */
> +#define BNAMES 2 /* fts_children, names only */
> +#define BREAD 3 /* fts_read */
> +
> +#if GNULIB_FTS_DEBUG
> +# include <inttypes.h>
> +# include <stdio.h>
> +bool fts_debug = false;
> +# define Dprintf(x) do { if (fts_debug) printf x; } while (false)
> +static void fd_ring_check (FTSOBJ const *);
> +static void fd_ring_print (FTSOBJ const *, FILE *, char const *);
> +#else
> +# define Dprintf(x)
> +# define fd_ring_check(x)
> +# define fd_ring_print(a, b, c)
> +#endif
> +
> +#define LEAVE_DIR(Fts, Ent, Tag) \
> + do \
> + { \
> + Dprintf ((" %s-leaving: %s\n", Tag, (Ent)->fts_path)); \
> + leave_dir (Fts, Ent); \
> + fd_ring_check (Fts); \
> + } \
> + while (false)
> +
> +static void
> +fd_ring_clear (I_ring *fd_ring)
> +{
> + while ( ! i_ring_empty (fd_ring))
> + {
> + int fd = i_ring_pop (fd_ring);
> + if (0 <= fd)
> + close (fd);
> + }
> +}
> +
> +/* Overload the fts_statp->st_size member (otherwise unused, when
> + fts_info is FTS_NSOK) to indicate whether fts_read should stat
> + this entry or not. */
> +static void
> +fts_set_stat_required (FTSENTRY *p, bool required)
> +{
> + fts_assert (p->fts_info == FTS_NSOK);
> + p->fts_statp->st_size = (required
> + ? FTS_STAT_REQUIRED
> + : FTS_NO_STAT_REQUIRED);
> +}
> +
> +/* Virtual fchdir. Advance SP's working directory file descriptor,
> + SP->fts_cwd_fd, to FD, and push the previous value onto the fd_ring.
> + CHDIR_DOWN_ONE is true if FD corresponds to an entry in the directory
> + open on sp->fts_cwd_fd; i.e., to move the working directory one level
> + down. */
> +static void
> +internal_function
> +cwd_advance_fd (FTSOBJ *sp, int fd, bool chdir_down_one)
> +{
> + int old = sp->fts_cwd_fd;
> + fts_assert (old != fd || old == AT_FDCWD);
> +
> + if (chdir_down_one)
> + {
> + /* Push "old" onto the ring.
> + If the displaced file descriptor is non-negative, close it. */
> + int prev_fd_in_slot = i_ring_push (&sp->fts_fd_ring, old);
> + fd_ring_print (sp, stderr, "post-push");
> + if (0 <= prev_fd_in_slot)
> + close (prev_fd_in_slot); /* ignore any close failure */
> + }
> + else if ( ! ISSET (FTS_NOCHDIR))
> + {
> + if (0 <= old)
> + close (old); /* ignore any close failure */
> + }
> +
> + sp->fts_cwd_fd = fd;
> +}
> +
> +/* Restore the initial, pre-traversal, "working directory".
> + In FTS_CWDFD mode, we merely call cwd_advance_fd, otherwise,
> + we may actually change the working directory.
> + Return 0 upon success. Upon failure, set errno and return nonzero. */
> +static int
> +restore_initial_cwd (FTSOBJ *sp)
> +{
> + int fail = FCHDIR (sp, ISSET (FTS_CWDFD) ? AT_FDCWD : sp->fts_rfd);
> + fd_ring_clear (&(sp->fts_fd_ring));
> + return fail;
> +}
> +
> +/* Open the directory DIR if possible, and return a file
> + descriptor. Return -1 and set errno on failure. It doesn't matter
> + whether the file descriptor has read or write access. */
> +
> +static int
> +internal_function
> +diropen (FTSOBJ const *sp, char const *dir)
> +{
> + int open_flags = (O_SEARCH | O_CLOEXEC | O_DIRECTORY | O_NOCTTY | O_NONBLOCK
> + | (ISSET (FTS_PHYSICAL) ? O_NOFOLLOW : 0));
> +
> + int fd = (ISSET (FTS_CWDFD)
> + ? openat (sp->fts_cwd_fd, dir, open_flags)
> + : open (dir, open_flags));
> + return fd;
> +}
> +
> +FTSOBJ *
> +FTS_OPEN (char * const *argv,
> + register int options,
> + int (*compar) (const FTSENTRY **, const FTSENTRY **))
> +{
> + /* Options check: glibc added other flags after FTS_NAMEONLY and
> + FTS_STOP, and they are assumed to be private. */
> + if (options & ~FTS_OPTIONMASK
> +#if _LIBC
> + || options & (FTS_NAMEONLY | FTS_STOP)
> +#endif
> + ) {
> + __set_errno (EINVAL);
> + return (NULL);
> + }
> + if ((options & FTS_NOCHDIR) && (options & FTS_CWDFD)) {
> + __set_errno (EINVAL);
> + return (NULL);
> + }
> +#if !_LIBC
> + if ( ! (options & (FTS_LOGICAL | FTS_PHYSICAL))) {
> + __set_errno (EINVAL);
> + return (NULL);
> + }
> +#else
> + /* glibc historically falls to FTS_PHYSICAL if no FTS_PHYSICAL or
> + FTS_LOGICAL is specified. */
> + if (! (options & (FTS_PHYSICAL | FTS_LOGICAL)))
> + options |= FTS_PHYSICAL;
> +#endif
> +
> + /* Allocate/initialize the stream */
> + register FTSOBJ *sp = calloc (1, sizeof *sp);
> + if (sp == NULL)
> + return (NULL);
> + sp->fts_compar = FTS_COMPAR_CAST(compar);
> + sp->fts_options = options;
> +
> + /* Logical walks turn on NOCHDIR; symbolic links are too hard. */
> + if (ISSET(FTS_LOGICAL)) {
> + SET(FTS_NOCHDIR);
> + CLR(FTS_CWDFD);
> + }
> +
> + /* Initialize fts_cwd_fd. */
> + sp->fts_cwd_fd = AT_FDCWD;
> + if ( ISSET(FTS_CWDFD) && ! HAVE_OPENAT_SUPPORT)
> + {
> + /* While it isn't technically necessary to open "." this
> + early, doing it here saves us the trouble of ensuring
> + later (where it'd be messier) that "." can in fact
> + be opened. If not, revert to FTS_NOCHDIR mode. */
> + int fd = open (".", O_SEARCH | O_CLOEXEC);
> + if (fd < 0)
> + {
> + /* Even if "." is unreadable, don't revert to FTS_NOCHDIR mode
> + on systems like Linux+PROC_FS, where our openat emulation
> + is good enough. Note: on a system that emulates
> + openat via /proc, this technique can still fail, but
> + only in extreme conditions, e.g., when the working
> + directory cannot be saved (i.e. save_cwd fails) --
> + and that happens on Linux only when "." is unreadable
> + and the CWD would be longer than PATH_MAX.
> + FIXME: once Linux kernel openat support is well established,
> + replace the above open call and this entire if/else block
> + with the body of the if-block below. */
> + if ( openat_needs_fchdir ())
> + {
> + SET(FTS_NOCHDIR);
> + CLR(FTS_CWDFD);
> + }
> + }
> + else
> + {
> + close (fd);
> + }
> + }
> +
> + /*
> + * Start out with 1K of file name space, and enough, in any case,
> + * to hold the user's file names.
> + */
> +#ifndef MAXPATHLEN
> +# define MAXPATHLEN 1024
> +#endif
> + {
> + size_t maxarglen = fts_maxarglen(argv);
> + if (! fts_palloc(sp, MAX(maxarglen, MAXPATHLEN)))
> + goto mem1;
> + }
> +
> + /* Allocate/initialize root's parent. */
> + FTSENTRY *parent = NULL;
> + if (*argv != NULL) {
> + if ((parent = fts_alloc(sp, "", 0)) == NULL)
> + goto mem2;
> + parent->fts_level = FTS_ROOTPARENTLEVEL;
> + }
> +
> + /* The classic fts implementation would call fts_stat with
> + a new entry for each iteration of the loop below.
> + If the comparison function is not specified or if the
> + FTS_DEFER_STAT option is in effect, don't stat any entry
> + in this loop. This is an attempt to minimize the interval
> + between the initial stat/lstat/fstatat and the point at which
> + a directory argument is first opened. This matters for any
> + directory command line argument that resides on a file system
> + without genuine i-nodes. If you specify FTS_DEFER_STAT along
> + with a comparison function, that function must not access any
> + data via the fts_statp pointer. */
> + bool defer_stat = (compar == NULL || ISSET(FTS_DEFER_STAT));
> +
> + /* Allocate/initialize root(s). */
> + register FTSENTRY *root;
> + register size_t nitems;
> + FTSENTRY *tmp = NULL; /* pacify gcc */
> + for (root = NULL, nitems = 0; *argv != NULL; ++argv, ++nitems) {
> + /* *Do* allow zero-length file names. */
> + size_t len = strlen(*argv);
> +
> + if ( ! (options & FTS_VERBATIM))
> + {
> + /* If there are two or more trailing slashes, trim all but one,
> + but don't change "//" to "/", and do map "///" to "/". */
> + char const *v = *argv;
> + if (2 < len && v[len - 1] == '/')
> + while (1 < len && v[len - 2] == '/')
> + --len;
> + }
> +
> + register FTSENTRY *p = fts_alloc(sp, *argv, len);
> + if (p == NULL)
> + goto mem3;
> + p->fts_level = FTS_ROOTLEVEL;
> + p->fts_parent = parent;
> + p->fts_accpath = p->fts_name;
> + /* Even when defer_stat is true, be sure to stat the first
> + command line argument, since fts_read (at least with
> + FTS_XDEV) requires that. */
> + if (defer_stat && root != NULL) {
> + p->fts_info = FTS_NSOK;
> + fts_set_stat_required(p, true);
> + } else {
> + p->fts_info = fts_stat(sp, p, false);
> + }
> +
> + /*
> + * If comparison routine supplied, traverse in sorted
> + * order; otherwise traverse in the order specified.
> + */
> + if (compar) {
> + p->fts_link = root;
> + root = p;
> + } else {
> + p->fts_link = NULL;
> + if (root == NULL)
> + tmp = root = p;
> + else {
> + tmp->fts_link = p;
> + tmp = p;
> + }
> + }
> + }
> + if (compar && nitems > 1)
> + root = fts_sort(sp, root, nitems);
> +
> + /*
> + * Allocate a dummy pointer and make fts_read think that we've just
> + * finished the node before the root(s); set p->fts_info to FTS_INIT
> + * so that everything about the "current" node is ignored.
> + */
> + if ((sp->fts_cur = fts_alloc(sp, "", 0)) == NULL)
> + goto mem3;
> + sp->fts_cur->fts_link = root;
> + sp->fts_cur->fts_info = FTS_INIT;
> + sp->fts_cur->fts_level = 1;
> + if (! setup_dir (sp))
> + goto mem3;
> +
> + /*
> + * If using chdir(2), grab a file descriptor pointing to dot to ensure
> + * that we can get back here; this could be avoided for some file names,
> + * but almost certainly not worth the effort. Slashes, symbolic links,
> + * and ".." are all fairly nasty problems. Note, if we can't get the
> + * descriptor we run anyway, just more slowly.
> + */
> + if (!ISSET(FTS_NOCHDIR) && !ISSET(FTS_CWDFD)
> + && (sp->fts_rfd = diropen (sp, ".")) < 0)
> + SET(FTS_NOCHDIR);
> +
> + i_ring_init (&sp->fts_fd_ring, -1);
> + return (sp);
> +
> +mem3: fts_lfree(root);
> + free(FTSENT_WRAPPER(parent));
> +mem2: free(sp->fts_path);
> +mem1: free(sp);
> + return (NULL);
> +}
> +
> +static void
> +internal_function
> +fts_load (FTSOBJ *sp, register FTSENTRY *p)
> +{
> + /*
> + * Load the stream structure for the next traversal. Since we don't
> + * actually enter the directory until after the preorder visit, set
> + * the fts_accpath field specially so the chdir gets done to the right
> + * place and the user can access the first node. From fts_open it's
> + * known that the file name will fit.
> + */
> + register size_t len = p->fts_pathlen = p->fts_namelen;
> + memmove(sp->fts_path, p->fts_name, len + 1);
> + register char *cp = strrchr(p->fts_name, '/');
> + if (cp && (cp != p->fts_name || cp[1])) {
> + len = strlen(++cp);
> + memmove(p->fts_name, cp, len + 1);
> + p->fts_namelen = len;
> + }
> + p->fts_accpath = p->fts_path = sp->fts_path;
> +}
> +
> +int
> +FTS_CLOSE (FTSOBJ *sp)
> +{
> + /*
> + * This still works if we haven't read anything -- the dummy structure
> + * points to the root list, so we step through to the end of the root
> + * list which has a valid parent pointer.
> + */
> + if (sp->fts_cur) {
> + register FTSENTRY *p;
> + for (p = sp->fts_cur; p->fts_level >= FTS_ROOTLEVEL;) {
> + register FTSENTRY *freep = p;
> + p = p->fts_link != NULL ? p->fts_link : p->fts_parent;
> + free(FTSENT_WRAPPER(freep));
> + }
> + free(FTSENT_WRAPPER(p));
> + }
> +
> + /* Free up child linked list, sort array, file name buffer. */
> + if (sp->fts_child)
> + fts_lfree(sp->fts_child);
> + free(sp->fts_array);
> + free(sp->fts_path);
> +
> + int saved_errno = 0;
> + if (ISSET(FTS_CWDFD))
> + {
> + if (0 <= sp->fts_cwd_fd)
> + if (close (sp->fts_cwd_fd))
> + saved_errno = errno;
> + }
> + else if (!ISSET(FTS_NOCHDIR))
> + {
> + /* Return to original directory, save errno if necessary. */
> + if (fchdir(sp->fts_rfd))
> + saved_errno = errno;
> +
> + /* If close fails, record errno only if saved_errno is zero,
> + so that we report the probably-more-meaningful fchdir errno. */
> + if (close (sp->fts_rfd))
> + if (saved_errno == 0)
> + saved_errno = errno;
> + }
> +
> + fd_ring_clear (&sp->fts_fd_ring);
> +
> + if (sp->fts_leaf_optimization_works_ht)
> + hash_free (sp->fts_leaf_optimization_works_ht);
> +
> + free_dir (sp);
> +
> + /* Free up the stream pointer. */
> + free(sp);
> +
> + /* Set errno and return. */
> + if (saved_errno) {
> + __set_errno (saved_errno);
> + return (-1);
> + }
> +
> + return (0);
> +}
> +
> +/* Minimum link count of a traditional Unix directory. When leaf
> + optimization is OK and a directory's st_nlink == MIN_DIR_NLINK,
> + then the directory has no subdirectories. */
> +enum { MIN_DIR_NLINK = 2 };
> +
> +/* Whether leaf optimization is OK for a directory. */
> +enum leaf_optimization
> + {
> + /* st_nlink is not reliable for this directory's subdirectories. */
> + NO_LEAF_OPTIMIZATION,
> +
> + /* st_nlink == 2 means the directory lacks subdirectories. */
> + OK_LEAF_OPTIMIZATION
> + };
> +
> +#if (defined __linux__ || defined __ANDROID__) \
> + && HAVE_SYS_VFS_H && HAVE_FSTATFS && HAVE_STRUCT_STATFS_F_TYPE
> +
> +# include <sys/vfs.h>
> +
> +/* Linux-specific constants from coreutils' src/fs.h */
> +# define S_MAGIC_AFS 0x5346414F
> +# define S_MAGIC_CIFS 0xFF534D42
> +# define S_MAGIC_LUSTRE 0x0BD00BD0
> +# define S_MAGIC_NFS 0x6969
> +# define S_MAGIC_PROC 0x9FA0
> +# define S_MAGIC_TMPFS 0x1021994
> +
> +# ifdef HAVE___FSWORD_T
> +typedef __fsword_t fsword;
> +# else
> +typedef long int fsword;
> +# endif
> +
> +/* Map a stat.st_dev number to a file system type number f_ftype. */
> +struct dev_type
> +{
> + dev_t st_dev;
> + fsword f_type;
> +};
> +
> +/* Use a tiny initial size. If a traversal encounters more than
> + a few devices, the cost of growing/rehashing this table will be
> + rendered negligible by the number of inodes processed. */
> +enum { DEV_TYPE_HT_INITIAL_SIZE = 13 };
> +
> +static size_t
> +dev_type_hash (void const *x, size_t table_size)
> +{
> + struct dev_type const *ax = x;
> + uintmax_t dev = ax->st_dev;
> + return dev % table_size;
> +}
> +
> +static bool
> +dev_type_compare (void const *x, void const *y)
> +{
> + struct dev_type const *ax = x;
> + struct dev_type const *ay = y;
> + return ax->st_dev == ay->st_dev;
> +}
> +
> +/* Return the file system type of P with file descriptor FD, or 0 if not known.
> + If FD is negative, P's file descriptor is unavailable.
> + Try to cache known values. */
> +
> +static fsword
> +filesystem_type (FTSENTRY const *p, int fd)
> +{
> + FTSOBJ *sp = FTSENT_FTS(p);
> +
> + /* If we're not in CWDFD mode, don't bother with this optimization,
> + since the caller is not serious about performance. */
> + if (!ISSET (FTS_CWDFD))
> + return 0;
> +
> + Hash_table *h = sp->fts_leaf_optimization_works_ht;
> + if (! h)
> + h = sp->fts_leaf_optimization_works_ht
> + = hash_initialize (DEV_TYPE_HT_INITIAL_SIZE, NULL, dev_type_hash,
> + dev_type_compare, free);
> +
> + if (h)
> + {
> + struct dev_type tmp;
> + tmp.st_dev = p->fts_statp->st_dev;
> + struct dev_type *ent = hash_lookup (h, &tmp);
> + if (ent)
> + return ent->f_type;
> + }
> +
> + /* Look-up failed. Query directly and cache the result. */
> + struct STRUCT_STATFS fs_buf;
> + if (fd < 0 || FSTATFS (fd, &fs_buf) != 0)
> + return 0;
> +
> + if (h)
> + {
> + struct dev_type *t2 = malloc (sizeof *t2);
> + if (t2)
> + {
> + t2->st_dev = p->fts_statp->st_dev;
> + t2->f_type = fs_buf.f_type;
> +
> + struct dev_type *ent = hash_insert (h, t2);
> + if (ent)
> + fts_assert (ent == t2);
> + else
> + free (t2);
> + }
> + }
> +
> + return fs_buf.f_type;
> +}
> +
> +/* Return true if sorting dirents on inode numbers is known to improve
> + traversal performance for the directory P with descriptor DIR_FD.
> + Return false otherwise. When in doubt, return true.
> + DIR_FD is negative if unavailable. */
> +static bool
> +dirent_inode_sort_may_be_useful (FTSENTRY const *p, int dir_fd)
> +{
> + /* Skip the sort only if we can determine efficiently
> + that skipping it is the right thing to do.
> + The cost of performing an unnecessary sort is negligible,
> + while the cost of *not* performing it can be O(N^2) with
> + a very large constant. */
> +
> + switch (filesystem_type (p, dir_fd))
> + {
> + case S_MAGIC_LUSTRE:
> + /* On Lustre, sorting directory entries interferes with its ability to
> + prefetch file metadata (via statahead). This would make a command
> + like 'du' around 9 times slower. See
> + <https://bugs.gnu.org/80106>. */
> + case S_MAGIC_CIFS:
> + case S_MAGIC_NFS:
> + case S_MAGIC_TMPFS:
> + /* On a file system of any of these types, sorting
> + is unnecessary, and hence wasteful. */
> + return false;
> +
> + default:
> + return true;
> + }
> +}
> +
> +/* Given an FTS entry P for a directory with descriptor DIR_FD,
> + return whether it is valid to apply leaf optimization.
> + The optimization is valid if a directory's st_nlink value equal
> + to MIN_DIR_NLINK means the directory has no subdirectories.
> + DIR_FD is negative if unavailable. */
> +static enum leaf_optimization
> +leaf_optimization (FTSENTRY const *p, int dir_fd)
> +{
> + switch (filesystem_type (p, dir_fd))
> + {
> + case 0:
> + /* Leaf optimization is unsafe if the file system type is unknown. */
> + FALLTHROUGH;
> + case S_MAGIC_AFS:
> + /* Although AFS mount points are not counted in st_nlink, they
> + act like directories. See <https://bugs.debian.org/143111>. */
> + FALLTHROUGH;
> + case S_MAGIC_CIFS:
> + /* Leaf optimization causes 'find' to abort. See
> + <https://lists.gnu.org/r/bug-gnulib/2018-04/msg00015.html>. */
> + FALLTHROUGH;
> + case S_MAGIC_NFS:
> + /* NFS provides usable dirent.d_type but not necessarily for all entries
> + of large directories, so as per <https://bugzilla.redhat.com/1252549>
> + NFS should return true. However st_nlink values are not accurate on
> + all implementations as per <https://bugzilla.redhat.com/1299169>. */
> + FALLTHROUGH;
> + case S_MAGIC_PROC:
> + /* Per <https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=143111> /proc
> + may have bogus stat.st_nlink values. */
> + return NO_LEAF_OPTIMIZATION;
> +
> + default:
> + return OK_LEAF_OPTIMIZATION;
> + }
> +}
> +
> +#else
> +static bool
> +dirent_inode_sort_may_be_useful (_GL_UNUSED FTSENTRY const *p,
> + _GL_UNUSED int dir_fd)
> +{
> + return true;
> +}
> +static enum leaf_optimization
> +leaf_optimization (_GL_UNUSED FTSENTRY const *p, _GL_UNUSED int dir_fd)
> +{
> + return NO_LEAF_OPTIMIZATION;
> +}
> +#endif
> +
> +/*
> + * Special case of "/" at the end of the file name so that slashes aren't
> + * appended which would cause file names to be written as "....//foo".
> + */
> +#define NAPPEND(p) \
> + (p->fts_path[p->fts_pathlen - 1] == '/' \
> + ? p->fts_pathlen - 1 : p->fts_pathlen)
> +
> +FTSENTRY *
> +FTS_READ (FTSOBJ *sp)
> +{
> + /* If finished or unrecoverable error, return NULL. */
> + if (sp->fts_cur == NULL || ISSET(FTS_STOP))
> + return (NULL);
> +
> + /* Set current node pointer. */
> + register FTSENTRY *p = sp->fts_cur;
> +
> + /* Save and zero out user instructions. */
> + register unsigned short int instr = p->fts_instr;
> + p->fts_instr = FTS_NOINSTR;
> +
> + /* Any type of file may be re-visited; re-stat and re-turn. */
> + if (instr == FTS_AGAIN) {
> + p->fts_info = fts_stat(sp, p, false);
> + return (p);
> + }
> + Dprintf (("fts_read: p=%s\n",
> + p->fts_info == FTS_INIT ? "" : p->fts_path));
> +
> + /*
> + * Following a symlink -- SLNONE test allows application to see
> + * SLNONE and recover. If indirecting through a symlink, have
> + * keep a pointer to current location. If unable to get that
> + * pointer, follow fails.
> + */
> + if (instr == FTS_FOLLOW &&
> + (p->fts_info == FTS_SL || p->fts_info == FTS_SLNONE)) {
> + p->fts_info = fts_stat(sp, p, true);
> + if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) {
> + if ((p->fts_symfd = diropen (sp, ".")) < 0) {
> + p->fts_errno = errno;
> + p->fts_info = FTS_ERR;
> + } else
> + p->fts_flags |= FTS_SYMFOLLOW;
> + }
> + goto check_for_dir;
> + }
> +
> + /* Directory in pre-order. */
> + if (p->fts_info == FTS_D) {
> + /* If skipped or crossed mount point, do post-order visit. */
> + if (instr == FTS_SKIP ||
> + (ISSET(FTS_XDEV) && p->fts_statp->st_dev != sp->fts_dev)) {
> + if (p->fts_flags & FTS_SYMFOLLOW)
> + (void)close(p->fts_symfd);
> + if (sp->fts_child) {
> + fts_lfree(sp->fts_child);
> + sp->fts_child = NULL;
> + }
> + p->fts_info = FTS_DP;
> + LEAVE_DIR (sp, p, "1");
> + return (p);
> + }
> +
> + /* Rebuild if only read the names and now traversing. */
> + if (sp->fts_child != NULL && ISSET(FTS_NAMEONLY)) {
> + CLR(FTS_NAMEONLY);
> + fts_lfree(sp->fts_child);
> + sp->fts_child = NULL;
> + }
> +
> + /*
> + * Cd to the subdirectory.
> + *
> + * If have already read and now fail to chdir, whack the list
> + * to make the names come out right, and set the parent errno
> + * so the application will eventually get an error condition.
> + * Set the FTS_DONTCHDIR flag so that when we logically change
> + * directories back to the parent we don't do a chdir.
> + *
> + * If haven't read do so. If the read fails, fts_build sets
> + * FTS_STOP or the fts_info field of the node.
> + */
> + if (sp->fts_child != NULL) {
> + if (fts_safe_changedir(sp, p, -1, p->fts_accpath)) {
> + p->fts_errno = errno;
> + p->fts_flags |= FTS_DONTCHDIR;
> + for (p = sp->fts_child; p != NULL;
> + p = p->fts_link)
> + p->fts_accpath =
> + p->fts_parent->fts_accpath;
> + }
> + } else if ((sp->fts_child = fts_build(sp, BREAD)) == NULL) {
> + if (ISSET(FTS_STOP))
> + return (NULL);
> + /* If fts_build's call to fts_safe_changedir failed
> + because it was not able to fchdir into a
> + subdirectory, tell the caller. */
> + if (p->fts_errno && p->fts_info != FTS_DNR)
> + p->fts_info = FTS_ERR;
> + LEAVE_DIR (sp, p, "2");
> + return (p);
> + }
> + p = sp->fts_child;
> + sp->fts_child = NULL;
> + goto name;
> + }
> +
> + /* Move to the next node on this level. */
> +next: ;
> + register FTSENTRY *tmp = p;
> +
> + /* If we have so many directory entries that we're reading them
> + in batches, and we've reached the end of the current batch,
> + read in a new batch. */
> + if (p->fts_link == NULL && FTSENT_DIRP(p->fts_parent))
> + {
> + p = tmp->fts_parent;
> + sp->fts_cur = p;
> + sp->fts_path[p->fts_pathlen] = '\0';
> +
> + if ((p = fts_build (sp, BREAD)) == NULL)
> + {
> + if (ISSET(FTS_STOP))
> + return NULL;
> + goto cd_dot_dot;
> + }
> +
> + free(FTSENT_WRAPPER(tmp));
> + goto name;
> + }
> +
> + if ((p = p->fts_link) != NULL) {
> + sp->fts_cur = p;
> + free(FTSENT_WRAPPER(tmp));
> +
> + /*
> + * If reached the top, return to the original directory (or
> + * the root of the tree), and load the file names for the next
> + * root.
> + */
> + if (p->fts_level == FTS_ROOTLEVEL) {
> + if (restore_initial_cwd(sp)) {
> + SET(FTS_STOP);
> + return (NULL);
> + }
> + free_dir(sp);
> + fts_load(sp, p);
> + if (! setup_dir(sp)) {
> + free_dir(sp);
> + return (NULL);
> + }
> + goto check_for_dir;
> + }
> +
> + /*
> + * User may have called fts_set on the node. If skipped,
> + * ignore. If followed, get a file descriptor so we can
> + * get back if necessary.
> + */
> + if (p->fts_instr == FTS_SKIP)
> + goto next;
> + if (p->fts_instr == FTS_FOLLOW) {
> + p->fts_info = fts_stat(sp, p, true);
> + if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) {
> + if ((p->fts_symfd = diropen (sp, ".")) < 0) {
> + p->fts_errno = errno;
> + p->fts_info = FTS_ERR;
> + } else
> + p->fts_flags |= FTS_SYMFOLLOW;
> + }
> + p->fts_instr = FTS_NOINSTR;
> + }
> +
> +name: {
> + register char *t = sp->fts_path + NAPPEND(p->fts_parent);
> + *t++ = '/';
> + memmove(t, p->fts_name, p->fts_namelen + 1);
> + }
> +check_for_dir:
> + sp->fts_cur = p;
> + if (p->fts_info == FTS_NSOK)
> + {
> + if (p->fts_statp->st_size == FTS_STAT_REQUIRED)
> + p->fts_info = fts_stat(sp, p, false);
> + else
> + fts_assert (p->fts_statp->st_size == FTS_NO_STAT_REQUIRED);
> + }
> +
> + /* Skip files with different device numbers when FTS_MOUNT
> + is set. */
> + if (ISSET (FTS_MOUNT) && p->fts_info != FTS_NS &&
> + p->fts_level != FTS_ROOTLEVEL &&
> + p->fts_statp->st_dev != sp->fts_dev)
> + goto next;
> +
> + if (p->fts_info == FTS_D)
> + {
> + /* Now that P->fts_statp is guaranteed to be valid, if
> + this is a command-line directory, record its device
> + number, to be used for FTS_MOUNT and FTS_XDEV. */
> + if (p->fts_level == FTS_ROOTLEVEL)
> + sp->fts_dev = p->fts_statp->st_dev;
> + Dprintf ((" entering: %s\n", p->fts_path));
> + if (! enter_dir (sp, p))
> + return NULL;
> + }
> + return p;
> + }
> +cd_dot_dot:
> +
> + /* Move up to the parent node. */
> + p = tmp->fts_parent;
> + sp->fts_cur = p;
> + free(FTSENT_WRAPPER(tmp));
> +
> + if (p->fts_level == FTS_ROOTPARENTLEVEL) {
> + /*
> + * Done; free everything up and set errno to 0 so the user
> + * can distinguish between error and EOF.
> + */
> + free(FTSENT_WRAPPER(p));
> + __set_errno (0);
> + return (sp->fts_cur = NULL);
> + }
> +
> + fts_assert (p->fts_info != FTS_NSOK);
> +
> + /* NUL terminate the file name. */
> + sp->fts_path[p->fts_pathlen] = '\0';
> +
> + /*
> + * Return to the parent directory. If at a root node, restore
> + * the initial working directory. If we came through a symlink,
> + * go back through the file descriptor. Otherwise, move up
> + * one level, via "..".
> + */
> + if (p->fts_level == FTS_ROOTLEVEL) {
> + if (restore_initial_cwd(sp)) {
> + p->fts_errno = errno;
> + SET(FTS_STOP);
> + }
> + } else if (p->fts_flags & FTS_SYMFOLLOW) {
> + if (FCHDIR(sp, p->fts_symfd)) {
> + p->fts_errno = errno;
> + SET(FTS_STOP);
> + }
> + (void)close(p->fts_symfd);
> + } else if (!(p->fts_flags & FTS_DONTCHDIR) &&
> + fts_safe_changedir(sp, p->fts_parent, -1, "..")) {
> + p->fts_errno = errno;
> + SET(FTS_STOP);
> + }
> +
> + /* If the directory causes a cycle, preserve the FTS_DC flag and keep
> + the corresponding dev/ino pair in the hash table. It is going to be
> + removed when leaving the original directory. */
> + if (p->fts_info != FTS_DC) {
> + p->fts_info = p->fts_errno ? FTS_ERR : FTS_DP;
> + if (p->fts_errno == 0)
> + LEAVE_DIR (sp, p, "3");
> + }
> + return ISSET(FTS_STOP) ? NULL : p;
> +}
> +
> +/*
> + * Fts_set takes the stream as an argument although it's not used in this
> + * implementation; it would be necessary if anyone wanted to add global
> + * semantics to fts using fts_set. An error return is allowed for similar
> + * reasons.
> + */
> +/* ARGSUSED */
> +int
> +FTS_SET (_GL_UNUSED FTSOBJ *sp, FTSENTRY *p, int instr)
> +{
> + if (instr != 0 && instr != FTS_AGAIN && instr != FTS_FOLLOW &&
> + instr != FTS_NOINSTR && instr != FTS_SKIP) {
> + __set_errno (EINVAL);
> + return (1);
> + }
> + p->fts_instr = instr;
> + return (0);
> +}
> +
> +FTSENTRY *
> +FTS_CHILDREN (FTSOBJ *sp, int instr)
> +{
> + if (instr != 0 && instr != FTS_NAMEONLY) {
> + __set_errno (EINVAL);
> + return (NULL);
> + }
> +
> + /* Set current node pointer. */
> + register FTSENTRY *p = sp->fts_cur;
> +
> + /*
> + * Errno set to 0 so user can distinguish empty directory from
> + * an error.
> + */
> + __set_errno (0);
> +
> + /* Fatal errors stop here. */
> + if (ISSET(FTS_STOP))
> + return (NULL);
> +
> + /* Return logical hierarchy of user's arguments. */
> + if (p->fts_info == FTS_INIT)
> + return (p->fts_link);
> +
> + /*
> + * If not a directory being visited in pre-order, stop here. Could
> + * allow FTS_DNR, assuming the user has fixed the problem, but the
> + * same effect is available with FTS_AGAIN.
> + */
> + if (p->fts_info != FTS_D /* && p->fts_info != FTS_DNR */)
> + return (NULL);
> +
> + /* Free up any previous child list. */
> + if (sp->fts_child != NULL)
> + fts_lfree(sp->fts_child);
> +
> + if (instr == FTS_NAMEONLY) {
> + SET(FTS_NAMEONLY);
> + instr = BNAMES;
> + } else
> + instr = BCHILD;
> +
> + /*
> + * If using chdir on a relative file name and called BEFORE fts_read
> + * does its chdir to the root of a traversal, we can lose -- we need to
> + * chdir into the subdirectory, and we don't know where the current
> + * directory is, so we can't get back so that the upcoming chdir by
> + * fts_read will work.
> + */
> + if (p->fts_level != FTS_ROOTLEVEL || p->fts_accpath[0] == '/' ||
> + ISSET(FTS_NOCHDIR))
> + return (sp->fts_child = fts_build(sp, instr));
> +
> + int fd = diropen (sp, ".");
> + if (fd < 0)
> + return (sp->fts_child = NULL);
> + sp->fts_child = fts_build(sp, instr);
> + if (ISSET(FTS_CWDFD))
> + {
> + cwd_advance_fd (sp, fd, true);
> + }
> + else
> + {
> + if (fchdir(fd))
> + {
> + int saved_errno = errno;
> + close (fd);
> + __set_errno (saved_errno);
> + return NULL;
> + }
> + close (fd);
> + }
> + return (sp->fts_child);
> +}
> +
> +/* A comparison function to sort on increasing inode number.
> + For some file system types, sorting either way makes a huge
> + performance difference for a directory with very many entries,
> + but sorting on increasing values is slightly better than sorting
> + on decreasing values. The difference is in the 5% range. */
> +static int
> +fts_compare_ino (FTSENTRY const **a, FTSENTRY const **b)
> +{
> + return _GL_CMP (a[0]->fts_statp->st_ino, b[0]->fts_statp->st_ino);
> +}
> +
> +/* Map the dirent.d_type value, DTYPE, to the corresponding stat.st_mode
> + S_IF* bit and set ST.st_mode, thus clearing all other bits in that field. */
> +static void
> +set_stat_type (struct STRUCT_STAT *st, unsigned int dtype)
> +{
> + mode_t type;
> + switch (dtype)
> + {
> + case DT_BLK:
> + type = S_IFBLK;
> + break;
> + case DT_CHR:
> + type = S_IFCHR;
> + break;
> + case DT_DIR:
> + type = S_IFDIR;
> + break;
> + case DT_FIFO:
> + type = S_IFIFO;
> + break;
> + case DT_LNK:
> + type = S_IFLNK;
> + break;
> + case DT_REG:
> + type = S_IFREG;
> + break;
> + case DT_SOCK:
> + type = S_IFSOCK;
> + break;
> + default:
> + type = 0;
> + }
> + st->st_mode = type;
> +}
> +
> +#define closedir_and_clear(dirp) \
> + do \
> + { \
> + closedir (dirp); \
> + dirp = NULL; \
> + } \
> + while (0)
> +
> +#define fts_opendir(file, Pdir_fd) \
> + OPENDIRAT((! ISSET(FTS_NOCHDIR) && ISSET(FTS_CWDFD) \
> + ? sp->fts_cwd_fd : AT_FDCWD), \
> + file, \
> + (((ISSET(FTS_PHYSICAL) \
> + && ! (ISSET(FTS_COMFOLLOW) \
> + && cur->fts_level == FTS_ROOTLEVEL)) \
> + ? O_NOFOLLOW : 0)), \
> + Pdir_fd)
> +
> +/*
> + * This is the tricky part -- do not casually change *anything* in here. The
> + * idea is to build the linked list of entries that are used by fts_children
> + * and fts_read. There are lots of special cases.
> + *
> + * The real slowdown in walking the tree is the stat calls. If FTS_NOSTAT is
> + * set and it's a physical walk (so that symbolic links can't be directories),
> + * we can do things quickly. First, if it's a 4.4BSD file system, the type
> + * of the file is in the directory entry. Otherwise, we assume that the number
> + * of subdirectories in a node is equal to the number of links to the parent.
> + * The former skips all stat calls. The latter skips stat calls in any leaf
> + * directories and for any files after the subdirectories in the directory have
> + * been found, cutting the stat calls by about 2/3.
> + */
> +static FTSENTRY *
> +internal_function
> +fts_build (register FTSOBJ *sp, int type)
> +{
> + FTSENTRY *cur = sp->fts_cur;
> + bool continue_readdir = !!FTSENT_DIRP(cur);
> +
> + /* When cur->fts_dirp is non-NULL, that means we should
> + continue calling readdir on that existing DIR* pointer
> + rather than opening a new one. */
> + int dir_fd;
> + if (continue_readdir)
> + {
> + DIR *dp = FTSENT_DIRP(cur);
> + dir_fd = dirfd (dp);
> + if (dir_fd < 0)
> + {
> + int dirfd_errno = errno;
> + closedir_and_clear (FTSENT_DIRP(cur));
> + if (type == BREAD)
> + {
> + cur->fts_info = FTS_DNR;
> + cur->fts_errno = dirfd_errno;
> + }
> + return NULL;
> + }
> + }
> + else
> + {
> + /* Open the directory for reading. If this fails, we're done.
> + If being called from fts_read, set the fts_info field. */
> + if ((FTSENT_DIRP (cur) = fts_opendir(cur->fts_accpath, &dir_fd)) == NULL)
> + {
> + if (type == BREAD)
> + {
> + cur->fts_info = FTS_DNR;
> + cur->fts_errno = errno;
> + }
> + return NULL;
> + }
> + /* Rather than calling fts_stat for each and every entry encountered
> + in the readdir loop (below), stat each directory only right after
> + opening it. */
> + bool stat_optimization = cur->fts_info == FTS_NSOK;
> +
> + if (stat_optimization
> + /* Also read the stat info again after opening a directory to
> + reveal eventual changes caused by a submount triggered by
> + the traversal. But do it only for utilities which use
> + FTS_TIGHT_CYCLE_CHECK. Therefore, only find and du
> + benefit/suffer from this feature for now. */
> + || ISSET (FTS_TIGHT_CYCLE_CHECK))
> + {
> + if (!stat_optimization)
> + LEAVE_DIR (sp, cur, "4");
> + if (FSTAT (dir_fd, cur->fts_statp) != 0)
> + {
> + int fstat_errno = errno;
> + closedir_and_clear (FTSENT_DIRP(cur));
> + if (type == BREAD)
> + {
> + cur->fts_errno = fstat_errno;
> + cur->fts_info = FTS_NS;
> + }
> + __set_errno (fstat_errno);
> + return NULL;
> + }
> + if (stat_optimization)
> + cur->fts_info = FTS_D;
> + else if (! enter_dir (sp, cur))
> + {
> + int saved_errno = errno;
> + closedir_and_clear (FTSENT_DIRP(cur));
> + __set_errno (saved_errno);
> + return NULL;
> + }
> + }
> + }
> +
> + /* Maximum number of readdir entries to read at one time. This
> + limitation is to avoid reading millions of entries into memory
> + at once. When an fts_compar function is specified, we have no
> + choice: we must read all entries into memory before calling that
> + function. But when no such function is specified, we can read
> + entries in batches that are large enough to help us with inode-
> + sorting, yet not so large that we risk exhausting memory. */
> + size_t max_entries = sp->fts_compar ? SIZE_MAX : FTS_MAX_READDIR_ENTRIES;
> +
> + /*
> + * If we're going to need to stat anything or we want to descend
> + * and stay in the directory, chdir. If this fails we keep going,
> + * but set a flag so we don't chdir after the post-order visit.
> + * We won't be able to stat anything, but we can still return the
> + * names themselves. Note, that since fts_read won't be able to
> + * chdir into the directory, it will have to return different file
> + * names than before, i.e. "a/b" instead of "b". Since the node
> + * has already been visited in pre-order, have to wait until the
> + * post-order visit to return the error. There is a special case
> + * here, if there was nothing to stat then it's not an error to
> + * not be able to stat. This is all fairly nasty. If a program
> + * needed sorted entries or stat information, they had better be
> + * checking FTS_NS on the returned nodes.
> + */
> + bool descend;
> + if (continue_readdir)
> + {
> + /* When resuming a short readdir run, we already have
> + the required dirp and dir_fd. */
> + descend = true;
> + }
> + else
> + {
> + /* Try to descend unless it is a names-only fts_children,
> + or the directory is known to lack subdirectories. */
> + descend = (type != BNAMES
> + && ! (ISSET (FTS_NOSTAT) && ISSET (FTS_PHYSICAL)
> + && ! ISSET (FTS_SEEDOT)
> + && cur->fts_statp->st_nlink == MIN_DIR_NLINK
> + && (leaf_optimization (cur, dir_fd)
> + != NO_LEAF_OPTIMIZATION)));
> + if (descend || type == BREAD)
> + {
> + if (ISSET(FTS_CWDFD))
> + dir_fd = fcntl (dir_fd, F_DUPFD_CLOEXEC, STDERR_FILENO + 1);
> + if (dir_fd < 0 || fts_safe_changedir(sp, cur, dir_fd, NULL)) {
> + if (descend && type == BREAD)
> + cur->fts_errno = errno;
> + cur->fts_flags |= FTS_DONTCHDIR;
> + descend = false;
> + closedir_and_clear(FTSENT_DIRP(cur));
> + if (ISSET(FTS_CWDFD) && 0 <= dir_fd)
> + close (dir_fd);
> + FTSENT_DIRP(cur) = NULL;
> + } else
> + descend = true;
> + }
> + }
> +
> + /*
> + * Figure out the max file name length that can be stored in the
> + * current buffer -- the inner loop allocates more space as necessary.
> + * We really wouldn't have to do the maxlen calculations here, we
> + * could do them in fts_read before returning the name, but it's a
> + * lot easier here since the length is part of the dirent structure.
> + *
> + * If not changing directories set a pointer so that can just append
> + * each new component into the file name.
> + */
> + size_t len = NAPPEND(cur);
> + char *cp;
> + if (ISSET(FTS_NOCHDIR)) {
> + cp = sp->fts_path + len;
> + *cp++ = '/';
> + } else {
> + /* GCC, you're too verbose. */
> + cp = NULL;
> + }
> + len++;
> + size_t maxlen = sp->fts_pathlen - len;
> +
> + ptrdiff_t level = cur->fts_level + 1;
> +
> + /* Read the directory, attaching each entry to the "link" pointer. */
> + bool doadjust = false;
> + register FTSENTRY *head = NULL;
> + FTSENTRY *tail = NULL;
> + register size_t nitems = 0;
> + bool sort_by_inode = false;
> + while (FTSENT_DIRP(cur)) {
> + __set_errno (0);
> + struct dirent *dp = readdir(FTSENT_DIRP(cur));
> + if (dp == NULL) {
> + /* Some readdir()s do not absorb ENOENT (dir
> + deleted but open). This bug was fixed in
> + glibc 2.3 (2002). */
> +#if ! (2 < __GLIBC__ + (3 <= __GLIBC_MINOR__))
> + if (errno == ENOENT)
> + errno = 0;
> +#endif
> + if (errno) {
> + cur->fts_errno = errno;
> + /* If we've not read any items yet, treat
> + the error as if we can't access the dir. */
> + cur->fts_info = (continue_readdir || nitems)
> + ? FTS_ERR : FTS_DNR;
> + }
> + closedir_and_clear(FTSENT_DIRP(cur));
> + break;
> + }
> + if (!ISSET(FTS_SEEDOT) && ISDOT(dp->d_name))
> + continue;
> +
> + size_t d_namelen = _D_EXACT_NAMLEN (dp);
> + register FTSENTRY *p = fts_alloc (sp, dp->d_name, d_namelen);
> + if (!p)
> + goto mem1;
> + if (d_namelen >= maxlen) {
> + /* include space for NUL */
> + uintptr_t oldaddr = (uintptr_t) sp->fts_path;
> + if (! fts_palloc(sp, d_namelen + len + 1)) {
> + /*
> + * No more memory. Save
> + * errno, free up the current structure and the
> + * structures already allocated.
> + */
> +mem1: ;
> + int saved_errno = errno;
> + free(FTSENT_WRAPPER(p));
> + fts_lfree(head);
> + closedir_and_clear(FTSENT_DIRP(cur));
> + cur->fts_info = FTS_ERR;
> + SET(FTS_STOP);
> + __set_errno (saved_errno);
> + return (NULL);
> + }
> + /* Did realloc() change the pointer? */
> + if (oldaddr != (uintptr_t) sp->fts_path) {
> + doadjust = true;
> + if (ISSET(FTS_NOCHDIR))
> + cp = sp->fts_path + len;
> + }
> + maxlen = sp->fts_pathlen - len;
> + }
> +
> + size_t new_len = len + d_namelen;
> + if (new_len < len) {
> + /*
> + * In the unlikely event that we would end up
> + * with a file name longer than SIZE_MAX, free up
> + * the current structure and the structures already
> + * allocated, then error out with ENAMETOOLONG.
> + */
> + free(FTSENT_WRAPPER(p));
> + fts_lfree(head);
> + closedir_and_clear(FTSENT_DIRP(cur));
> + cur->fts_info = FTS_ERR;
> + SET(FTS_STOP);
> + __set_errno (ENAMETOOLONG);
> + return (NULL);
> + }
> + p->fts_level = level;
> + p->fts_parent = sp->fts_cur;
> + p->fts_pathlen = new_len;
> +
> + /* Store dirent.d_ino, in case we need to sort
> + entries before processing them. */
> + p->fts_statp->st_ino = D_INO (dp);
> +
> + /* Build a file name for fts_stat to stat. */
> + if (ISSET(FTS_NOCHDIR)) {
> + p->fts_accpath = p->fts_path;
> + memmove(cp, p->fts_name, p->fts_namelen + 1);
> + } else
> + p->fts_accpath = p->fts_name;
> +
> + if (sp->fts_compar == NULL || ISSET(FTS_DEFER_STAT)) {
> + /* Record what fts_read will have to do with this
> + entry. In many cases, it will simply fts_stat it,
> + but we can take advantage of any d_type information
> + to optimize away the unnecessary stat calls. I.e.,
> + if FTS_NOSTAT is in effect, we don't need device
> + numbers unconditionally (FTS_MOUNT) and we're not
> + following symlinks (FTS_PHYSICAL) and d_type
> + indicates this is *not* a directory, then we won't
> + have to stat it at all. If it *is* a directory,
> + then (currently) we stat it regardless, in order to
> + get device and inode numbers. Some day we might
> + optimize that away, too, for directories where
> + d_ino is known to be valid. */
> + bool skip_stat = (ISSET(FTS_NOSTAT)
> + && DT_IS_KNOWN(dp)
> + && ! DT_MUST_BE(dp, DT_DIR)
> + && (ISSET(FTS_PHYSICAL)
> + || ! DT_MUST_BE(dp, DT_LNK))
> + && ! ISSET(FTS_MOUNT));
> + p->fts_info = FTS_NSOK;
> + /* Propagate dirent.d_type information back
> + to caller, when possible. */
> + set_stat_type (p->fts_statp, D_TYPE (dp));
> + fts_set_stat_required(p, !skip_stat);
> + } else {
> + p->fts_info = fts_stat(sp, p, false);
> + }
> +
> + /* We walk in directory order so "ls -f" doesn't get upset. */
> + p->fts_link = NULL;
> + if (head == NULL)
> + head = tail = p;
> + else {
> + tail->fts_link = p;
> + tail = p;
> + }
> +
> + /* If there are many entries, no sorting function has been
> + specified, and this file system is of a type that may be
> + slow with a large number of entries, arrange to sort the
> + directory entries on increasing inode numbers.
> +
> + The NITEMS comparison uses ==, not >, because the test
> + needs to be tried at most once once, and NITEMS will exceed
> + the threshold after it is incremented below. */
> + if (nitems == _FTS_INODE_SORT_DIR_ENTRIES_THRESHOLD
> + && !sp->fts_compar)
> + sort_by_inode = dirent_inode_sort_may_be_useful (cur, dir_fd);
> +
> + ++nitems;
> + if (max_entries <= nitems) {
> + /* When there are too many dir entries, leave
> + fts_dirp open, so that a subsequent fts_read
> + can take up where we leave off. */
> + break;
> + }
> + }
> +
> + /*
> + * If realloc() changed the address of the file name, adjust the
> + * addresses for the rest of the tree and the dir list.
> + */
> + if (doadjust)
> + fts_padjust(sp, head);
> +
> + /*
> + * If not changing directories, reset the file name back to original
> + * state.
> + */
> + if (ISSET(FTS_NOCHDIR)) {
> + if (len == sp->fts_pathlen || nitems == 0)
> + --cp;
> + *cp = '\0';
> + }
> +
> + /*
> + * If descended after called from fts_children or after called from
> + * fts_read and nothing found, get back. At the root level we use
> + * the saved fd; if one of fts_open()'s arguments is a relative name
> + * to an empty directory, we wind up here with no other way back. If
> + * can't get back, we're done.
> + */
> + if (!continue_readdir && descend && (type == BCHILD || !nitems) &&
> + (cur->fts_level == FTS_ROOTLEVEL
> + ? restore_initial_cwd(sp)
> + : fts_safe_changedir(sp, cur->fts_parent, -1, ".."))) {
> + cur->fts_info = FTS_ERR;
> + SET(FTS_STOP);
> + fts_lfree(head);
> + return (NULL);
> + }
> +
> + /* If didn't find anything, return NULL. */
> + if (!nitems) {
> + if (type == BREAD
> + && cur->fts_info != FTS_DNR && cur->fts_info != FTS_ERR)
> + cur->fts_info = FTS_DP;
> + fts_lfree(head);
> + return (NULL);
> + }
> +
> + if (sort_by_inode) {
> + sp->fts_compar = FTS_COMPAR_CAST (fts_compare_ino);
> + head = fts_sort (sp, head, nitems);
> + sp->fts_compar = NULL;
> + }
> +
> + /* Sort the entries. */
> + if (sp->fts_compar && nitems > 1)
> + head = fts_sort(sp, head, nitems);
> + return (head);
> +}
> +
> +#if GNULIB_FTS_DEBUG
> +
> +struct devino {
> + intmax_t dev, ino;
> +};
> +#define PRINT_DEVINO "(%jd,%jd)"
> +
> +static struct devino
> +getdevino (int fd)
> +{
> + struct STRUCT_STAT st;
> + return (fd == AT_FDCWD
> + ? (struct devino) { -1, 0 }
> + : FSTAT (fd, &st) == 0
> + ? (struct devino) { st.st_dev, st.st_ino }
> + : (struct devino) { -1, errno });
> +}
> +
> +/* Walk ->fts_parent links starting at E_CURR, until the root of the
> + current hierarchy. There should be a directory with dev/inode
> + matching those of AD. If not, print a lot of diagnostics. */
> +static void
> +find_matching_ancestor (FTSENTRY const *e_curr, struct Active_dir const *ad)
> +{
> + for (FTSENTRY const *ent = e_curr;
> + ent->fts_level >= FTS_ROOTLEVEL;
> + ent = ent->fts_parent)
> + {
> + if (ad->ino == ent->fts_statp->st_ino
> + && ad->dev == ent->fts_statp->st_dev)
> + return;
> + }
> + printf ("ERROR: tree dir, %s, not active\n", ad->fts_ent->fts_accpath);
> + printf ("active dirs:\n");
> + for (FTSENTRY const *ent = e_curr;
> + ent->fts_level >= FTS_ROOTLEVEL;
> + ent = ent->fts_parent)
> + printf (" %s(%"PRIuMAX"/%"PRIuMAX") to %s(%"PRIuMAX"/%"PRIuMAX")...\n",
> + ad->fts_ent->fts_accpath,
> + (uintmax_t) ad->dev,
> + (uintmax_t) ad->ino,
> + ent->fts_accpath,
> + (uintmax_t) ent->fts_statp->st_dev,
> + (uintmax_t) ent->fts_statp->st_ino);
> +}
> +
> +void
> +fts_cross_check (FTSOBJ const *sp)
> +{
> + if ( ! ISSET (FTS_TIGHT_CYCLE_CHECK))
> + return;
> +
> + FTSENTRY const *ent = sp->fts_cur;
> +
> + Dprintf (("fts-cross-check cur=%s\n", ent->fts_path));
> + /* Make sure every parent dir is in the tree. */
> + for (FTSENTRY const *t = ent->fts_parent;
> + t->fts_level >= FTS_ROOTLEVEL;
> + t = t->fts_parent)
> + {
> + struct Active_dir ad;
> + ad.ino = t->fts_statp->st_ino;
> + ad.dev = t->fts_statp->st_dev;
> + if ( ! hash_lookup (sp->fts_cycle.ht, &ad))
> + printf ("ERROR: active dir, %s, not in tree\n", t->fts_path);
> + }
> +
> + /* Make sure every dir in the tree is an active dir.
> + But ENT is not necessarily a directory. If so, just skip this part. */
> + if (ent->fts_parent->fts_level >= FTS_ROOTLEVEL
> + && (ent->fts_info == FTS_DP
> + || ent->fts_info == FTS_D))
> + for (struct Active_dir *ad = hash_get_first (sp->fts_cycle.ht);
> + ad != NULL;
> + ad = hash_get_next (sp->fts_cycle.ht, ad))
> + {
> + find_matching_ancestor (ent, ad);
> + }
> +}
> +
> +static bool
> +same_fd (int fd1, int fd2)
> +{
> + struct STRUCT_STAT sb1, sb2;
> + return (FSTAT (fd1, &sb1) == 0
> + && FSTAT (fd2, &sb2) == 0
> + && psame_inode (&sb1, &sb2));
> +}
> +
> +static void
> +fd_ring_print (FTSOBJ const *sp, FILE *stream, char const *msg)
> +{
> + if (!fts_debug)
> + return;
> + I_ring const *fd_ring = &sp->fts_fd_ring;
> + struct devino cwd = getdevino (sp->fts_cwd_fd);
> + fprintf (stream, "=== %s ========== "PRINT_DEVINO"\n", msg, cwd.dev, cwd.ino);
> + if (i_ring_empty (fd_ring))
> + return;
> +
> + unsigned int i = fd_ring->ir_front;
> + while (true)
> + {
> + int fd = fd_ring->ir_data[i];
> + if (fd < 0)
> + fprintf (stream, "%u: %d:\n", i, fd);
> + else
> + {
> + struct devino wd = getdevino (fd);
> + fprintf (stream, "%u: %d: "PRINT_DEVINO"\n", i, fd, wd.dev, wd.ino);
> + }
> + if (i == fd_ring->ir_back)
> + break;
> + i = (i + I_RING_SIZE - 1) % I_RING_SIZE;
> + }
> +}
> +
> +/* Ensure that each file descriptor on the fd_ring matches a
> + parent, grandparent, etc. of the current working directory. */
> +static void
> +fd_ring_check (FTSOBJ const *sp)
> +{
> + if (!fts_debug)
> + return;
> +
> + /* Make a writable copy. */
> + I_ring fd_w = sp->fts_fd_ring;
> +
> + int cwd_fd = sp->fts_cwd_fd;
> + cwd_fd = fcntl (cwd_fd, F_DUPFD_CLOEXEC, STDERR_FILENO + 1);
> + struct devino dot = getdevino (cwd_fd);
> + fprintf (stderr, "===== check ===== cwd: "PRINT_DEVINO"\n",
> + dot.dev, dot.ino);
> + while ( ! i_ring_empty (&fd_w))
> + {
> + int fd = i_ring_pop (&fd_w);
> + if (0 <= fd)
> + {
> + int open_flags = O_SEARCH | O_CLOEXEC;
> + int parent_fd = openat (cwd_fd, "..", open_flags);
> + if (parent_fd < 0)
> + {
> + // Warn?
> + break;
> + }
> + if (!same_fd (fd, parent_fd))
> + {
> + struct devino cwd = getdevino (fd);
> + fprintf (stderr, "ring : "PRINT_DEVINO"\n", cwd.dev, cwd.ino);
> + struct devino c2 = getdevino (parent_fd);
> + fprintf (stderr, "parent: "PRINT_DEVINO"\n", c2.dev, c2.ino);
> + fts_assert (0);
> + }
> + close (cwd_fd);
> + cwd_fd = parent_fd;
> + }
> + }
> + close (cwd_fd);
> +}
> +#endif
> +
> +static unsigned short int
> +internal_function
> +fts_stat(FTSOBJ *sp, register FTSENTRY *p, bool follow)
> +{
> + if (ISSET (FTS_LOGICAL)
> + || (ISSET (FTS_COMFOLLOW) && p->fts_level == FTS_ROOTLEVEL))
> + follow = true;
> +
> + struct STRUCT_STAT *sbp = p->fts_statp;
> +
> + /*
> + * If doing a logical walk, or application requested FTS_FOLLOW, do
> + * a stat(2). If that fails, check for a nonexistent symlink. If
> + * fail, set the errno from the stat call.
> + */
> + int flags = follow ? 0 : AT_SYMLINK_NOFOLLOW;
> + if (FSTATAT (sp->fts_cwd_fd, p->fts_accpath, sbp, flags) < 0)
> + {
> + if (follow && errno == ENOENT
> + && 0 <= FSTATAT (sp->fts_cwd_fd, p->fts_accpath, sbp,
> + AT_SYMLINK_NOFOLLOW))
> + {
> + __set_errno (0);
> + return FTS_SLNONE;
> + }
> +
> + p->fts_errno = errno;
> + memset (sbp, 0, sizeof *sbp);
> + return FTS_NS;
> + }
> +
> + if (S_ISDIR(sbp->st_mode)) {
> + if (ISDOT(p->fts_name)) {
> + /* Command-line "." and ".." are real directories. */
> + return (p->fts_level == FTS_ROOTLEVEL ? FTS_D : FTS_DOT);
> + }
> +
> + return (FTS_D);
> + }
> + if (S_ISLNK(sbp->st_mode))
> + return (FTS_SL);
> + if (S_ISREG(sbp->st_mode))
> + return (FTS_F);
> + return (FTS_DEFAULT);
> +}
> +
> +static int
> +fts_compar (void const *a, void const *b)
> +{
> + /* Convert A and B to the correct types, to pacify the compiler, and
> + for portability to bizarre hosts where "void const *" and "FTSENT
> + const **" differ in runtime representation. The comparison
> + function cannot modify *a and *b, but there is no compile-time
> + check for this. */
> + FTSENTRY const **pa = (FTSENTRY const **) a;
> + FTSENTRY const **pb = (FTSENTRY const **) b;
> + return FTSENT_FTS(pa[0])->fts_compar (pa, pb);
> +}
> +
> +static FTSENTRY *
> +internal_function
> +fts_sort (FTSOBJ *sp, FTSENTRY *head, register size_t nitems)
> +{
> + register FTSENTRY **ap, *p;
> +
> + /* On most modern hosts, void * and FTSENT ** have the same
> + run-time representation, and one can convert sp->fts_compar to
> + the type qsort expects without problem. Use the heuristic that
> + this is OK if the two pointer types are the same size, and if
> + converting FTSENT ** to uintptr_t is the same as converting
> + FTSENT ** to void * and then to uintptr_t. This heuristic isn't
> + valid in general but we don't know of any counterexamples. */
> + FTSENTRY *dummy;
> + int (*compare) (void const *, void const *) =
> + ((sizeof &dummy == sizeof (void *)
> + && (uintptr_t) &dummy == (uintptr_t) (void *) &dummy)
> + ? (int (*) (void const *, void const *)) sp->fts_compar
> + : fts_compar);
> +
> + /*
> + * Construct an array of pointers to the structures and call qsort(3).
> + * Reassemble the array in the order returned by qsort. If unable to
> + * sort for memory reasons, return the directory entries in their
> + * current order. Allocate enough space for the current needs plus
> + * 40 so don't realloc one entry at a time.
> + */
> + if (nitems > sp->fts_nitems) {
> + sp->fts_nitems = nitems + 40;
> + FTSENTRY **a;
> + if (! (a = reallocarray (sp->fts_array,
> + sp->fts_nitems, sizeof *a))) {
> + free(sp->fts_array);
> + sp->fts_array = NULL;
> + sp->fts_nitems = 0;
> + return (head);
> + }
> + sp->fts_array = a;
> + }
> + for (ap = sp->fts_array, p = head; p; p = p->fts_link)
> + *ap++ = p;
> + qsort((void *)sp->fts_array, nitems, sizeof(FTSENTRY *), compare);
> + for (head = *(ap = sp->fts_array); --nitems; ++ap)
> + ap[0]->fts_link = ap[1];
> + ap[0]->fts_link = NULL;
> + return (head);
> +}
> +
> +static FTSENTRY *
> +internal_function
> +fts_alloc (FTSOBJ *sp, const char *name, register size_t namelen)
> +{
> + /*
> + * The file name is a variable length array. Allocate the FTSENT
> + * structure and the file name in one chunk.
> + */
> + size_t len = FLEXSIZEOF(FTSENT, fts_name, namelen + 1);
> + register FTSENTRY *p;
> +#if !_LIBC
> + p = malloc(len);
> + if (p == NULL)
> + return (NULL);
> +#else
> + /*
> + * For glibc, we use a wrapper struct to provide the extra required
> + * fields without changing the FSENT layout.
> + */
> + len += sizeof (struct FTSENT_wrapper);
> + struct FTSENT_wrapper *wrapper = malloc(len);
> + if (wrapper == NULL)
> + return (NULL);
> + p = &wrapper->ent;
> + p->fts_statp = &wrapper->fts_stat;
> +#endif
> +
> + /* Copy the name and guarantee NUL termination. */
> + memcpy(p->fts_name, name, namelen);
> + p->fts_name[namelen] = '\0';
> +
> + p->fts_namelen = namelen;
> + FTSENT_FTS(p)= sp;
> + p->fts_path = sp->fts_path;
> + p->fts_errno = 0;
> + FTSENT_DIRP(p) = NULL;
> + p->fts_flags = 0;
> + p->fts_instr = FTS_NOINSTR;
> + p->fts_number = 0;
> + p->fts_pointer = NULL;
> + return (p);
> +}
> +
> +static void
> +internal_function
> +fts_lfree (register FTSENTRY *head)
> +{
> + int saved_errno = errno;
> +
> + /* Free a linked list of structures. */
> + register FTSENTRY *p;
> + while ((p = head)) {
> + head = head->fts_link;
> + if (FTSENT_DIRP(p))
> + closedir (FTSENT_DIRP(p));
> + free(FTSENT_WRAPPER(p));
> + }
> +
> + __set_errno (saved_errno);
> +}
> +
> +/*
> + * Allow essentially unlimited file name lengths; find, rm, ls should
> + * all work on any tree. Most systems will allow creation of file
> + * names much longer than MAXPATHLEN, even though the kernel won't
> + * resolve them. Add the size (not just what's needed) plus 256 bytes
> + * so don't realloc the file name 2 bytes at a time.
> + */
> +static bool
> +internal_function
> +fts_palloc (FTSOBJ *sp, size_t more)
> +{
> + size_t new_len = sp->fts_pathlen + more + 256;
> +
> + /*
> + * See if fts_pathlen would overflow.
> + */
> + if (new_len < sp->fts_pathlen) {
> + free(sp->fts_path);
> + sp->fts_path = NULL;
> + __set_errno (ENAMETOOLONG);
> + return false;
> + }
> + sp->fts_pathlen = new_len;
> + char *p = realloc(sp->fts_path, sp->fts_pathlen);
> + if (p == NULL) {
> + free(sp->fts_path);
> + sp->fts_path = NULL;
> + return false;
> + }
> + sp->fts_path = p;
> + return true;
> +}
> +
> +/*
> + * When the file name is realloc'd, have to fix all of the pointers in
> + * structures already returned.
> + */
> +static void
> +internal_function
> +fts_padjust (FTSOBJ *sp, FTSENTRY *head)
> +{
> + char *addr = sp->fts_path;
> +
> + /* This code looks at bit-patterns of freed pointers to
> + relocate them, so it relies on undefined behavior. If this
> + trick does not work on your platform, please report a bug. */
> +
> +#define ADJUST(p) do { \
> + uintptr_t old_accpath = (uintptr_t) (p)->fts_accpath; \
> + if (old_accpath != (uintptr_t) (p)->fts_name) { \
> + (p)->fts_accpath = \
> + addr + (old_accpath - (uintptr_t) (p)->fts_path); \
> + } \
> + (p)->fts_path = addr; \
> +} while (0)
> + /* Adjust the current set of children. */
> + for (FTSENTRY *p = sp->fts_child; p; p = p->fts_link)
> + ADJUST(p);
> +
> + /* Adjust the rest of the tree, including the current level. */
> + for (FTSENTRY *p = head; p->fts_level >= FTS_ROOTLEVEL;) {
> + ADJUST(p);
> + p = p->fts_link ? p->fts_link : p->fts_parent;
> + }
> +}
> +
> +static size_t
> +internal_function _GL_ATTRIBUTE_PURE
> +fts_maxarglen (char * const *argv)
> +{
> + size_t max;
> +
> + for (max = 0; *argv; ++argv) {
> + size_t len = strlen(*argv);
> + if (len > max)
> + max = len;
> + }
> + return (max + 1);
> +}
> +
> +/*
> + * Change to dir specified by fd or file name without getting
> + * tricked by someone changing the world out from underneath us.
> + * Assumes p->fts_statp->st_dev and p->fts_statp->st_ino are filled in.
> + * If FD is non-negative, expect it to be used after this function returns,
> + * and to be closed eventually. So don't pass e.g., 'dirfd(dirp)' and then
> + * do closedir(dirp), because that would invalidate the saved FD.
> + * Upon failure, close FD immediately and return nonzero.
> + */
> +static int
> +internal_function
> +fts_safe_changedir (FTSOBJ *sp, FTSENTRY *p, int fd, char const *dir)
> +{
> + fts_assert (0 <= fd || dir != NULL);
> + bool is_dotdot = dir && streq (dir, "..");
> +
> + /* This clause handles the unusual case in which FTS_NOCHDIR
> + is specified, along with FTS_CWDFD. In that case, there is
> + no need to change even the virtual cwd file descriptor.
> + However, if FD is non-negative, we do close it here. */
> + if (ISSET (FTS_NOCHDIR))
> + {
> + if (ISSET (FTS_CWDFD) && 0 <= fd)
> + close (fd);
> + return 0;
> + }
> +
> + if (fd < 0 && is_dotdot && ISSET (FTS_CWDFD))
> + {
> + /* When possible, skip the diropen and subsequent fstat+dev/ino
> + comparison. I.e., when changing to parent directory
> + (chdir ("..")), use a file descriptor from the ring and
> + save the overhead of diropen+fstat, as well as avoiding
> + failure when we lack "x" access to the virtual cwd. */
> + if ( ! i_ring_empty (&sp->fts_fd_ring))
> + {
> + int parent_fd;
> + fd_ring_print (sp, stderr, "pre-pop");
> + parent_fd = i_ring_pop (&sp->fts_fd_ring);
> + if (0 <= parent_fd)
> + {
> + fd = parent_fd;
> + dir = NULL;
> + }
> + }
> + }
> +
> + int newfd = fd;
> + if (fd < 0 && (newfd = diropen (sp, dir)) < 0)
> + return -1;
> +
> + /* The following dev/inode check is necessary if we're doing a
> + "logical" traversal (through symlinks, a la chown -L), if the
> + system lacks O_NOFOLLOW support, or if we're changing to ".."
> + (but not via a popped file descriptor). When changing to the
> + name "..", O_NOFOLLOW can't help. In general, when the target is
> + not "..", diropen's use of O_NOFOLLOW ensures we don't mistakenly
> + follow a symlink, so we can avoid the expense of this fstat. */
> + int ret;
> + if (ISSET(FTS_LOGICAL) || ! HAVE_WORKING_O_NOFOLLOW
> + || (dir && streq (dir, "..")))
> + {
> + struct STRUCT_STAT sb;
> + if (FSTAT (newfd, &sb))
> + {
> + ret = -1;
> + goto bail;
> + }
> + if (p->fts_statp->st_dev != sb.st_dev
> + || p->fts_statp->st_ino != sb.st_ino)
> + {
> + __set_errno (ENOENT); /* disinformation */
> + ret = -1;
> + goto bail;
> + }
> + }
> +
> + if (ISSET(FTS_CWDFD))
> + {
> + cwd_advance_fd (sp, newfd, ! is_dotdot);
> + return 0;
> + }
> +
> + ret = fchdir(newfd);
> +bail:
> + if (fd < 0)
> + {
> + int oerrno = errno;
> + (void)close(newfd);
> + __set_errno (oerrno);
> + }
> + return ret;
> +}
> diff --git a/io/fts.c b/io/fts.c
> index 3825f0aeb4..d3b8e5a100 100644
> --- a/io/fts.c
> +++ b/io/fts.c
> @@ -1,2208 +1,23 @@
> -/* Traverse a file hierarchy.
> +/* File tree traversal functions LFS version.
> + Copyright (C) 2026 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
>
> - Copyright (C) 2004-2026 Free Software Foundation, Inc.
> + 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.
>
> - This file 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.
> -
> - This file is distributed in the hope that it will be useful,
> + 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.
> + 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 this program. If not, see <https://www.gnu.org/licenses/>. */
> + 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/>. */
>
> -/*-
> - * Copyright (c) 1990, 1993, 1994
> - * The Regents of the University of California. All rights reserved.
> - *
> - * Redistribution and use in source and binary forms, with or without
> - * modification, are permitted provided that the following conditions
> - * are met:
> - * 1. Redistributions of source code must retain the above copyright
> - * notice, this list of conditions and the following disclaimer.
> - * 2. Redistributions in binary form must reproduce the above copyright
> - * notice, this list of conditions and the following disclaimer in the
> - * documentation and/or other materials provided with the distribution.
> - * 4. Neither the name of the University nor the names of its contributors
> - * may be used to endorse or promote products derived from this software
> - * without specific prior written permission.
> - *
> - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
> - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
> - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
> - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
> - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
> - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
> - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
> - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
> - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
> - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
> - * SUCH DAMAGE.
> - */
> +#include <sys/types.h>
>
> -#include <config.h>
> -
> -#if defined LIBC_SCCS && !defined GCC_LINT && !defined lint
> -static char sccsid[] = "@(#)fts.c 8.6 (Berkeley) 8/14/94";
> +#ifndef __OFF_T_MATCHES_OFF64_T
> +# include "io/fts-common.c"
> #endif
> -
> -#if _LIBC
> -# include <fts.h>
> -#else
> -# include "fts_.h"
> -#endif
> -#if _LIBC || HAVE_SYS_PARAM_H
> -# include <sys/param.h>
> -#endif
> -#include <sys/stat.h>
> -#include <fcntl.h>
> -#include <errno.h>
> -#include <stddef.h>
> -#include <stdint.h>
> -#include <stdlib.h>
> -#include <string.h>
> -#include <unistd.h>
> -
> -/* Support for the LFS API version. */
> -#ifndef FTS_OPEN
> -# define FTS_OPEN fts_open
> -# define FTS_CLOSE fts_close
> -# define FTS_READ fts_read
> -# define FTS_SET fts_set
> -# define FTS_CHILDREN fts_children
> -# define FTSOBJ FTS
> -# define FTSENTRY FTSENT
> -# define INO_T ino_t
> -# define STRUCT_STAT stat
> -# define FSTAT __fstat
> -# define FSTATAT __fstatat
> -# define STRUCT_STATFS statfs
> -# define FSTATFS __fstatfs
> -#endif
> -
> -#if ! _LIBC
> -# include "attribute.h"
> -# include "fcntl--.h"
> -# include "openat.h"
> -# include "opendirat.h"
> -# include "same-inode.h"
> -# define OPENDIRAT opendirat
> -# define FTSENT_WRAPPER(__p) __p
> -# define FTS_COMPAR_CAST(__fn) __fn
> -#else
> -# include <stdbool.h>
> -
> -# define internal_function
> -# define FALLTHROUGH ; [[fallthrough]]
> -# define HAVE_STRUCT_DIRENT_D_TYPE 1
> -# define GNULIB_FTS_DEBUG 0
> -# ifdef O_PATH
> -# define O_SEARCH O_PATH
> -# else
> -# define O_SEARCH O_RDONLY
> -# endif
> -# define HAVE_SYS_VFS_H 1
> -# define HAVE_FSTATFS 1
> -# define HAVE_STRUCT_STATFS_F_TYPE 1
> -# define HAVE_OPENAT 1
> -# define HAVE_WORKING_O_NOFOLLOW 1
> -# define _GL_CMP(a, b) ((a) < (b) ? -1 : (a) > (b))
> -# define OPENDIRAT __opendirat
> -
> -static inline bool openat_needs_fchdir (void)
> -{
> - return false;
> -}
> -
> -static inline bool streq (const char *s1, const char *s2)
> -{
> - return strcmp (s1, s2) == 0;
> -}
> -# define reallocarray __libc_reallocarray
> -# define fchdir __fchdir
> -# define close __close
> -# define closedir __closedir
> -# define fcntl __fcntl
> -# define readdir __readdir
> -# ifndef dirfd
> -# define dirfd __dirfd
> -# endif
> -# define open __open
> -# define openat __openat
> -
> -# include "cycle-check.c"
> -# include "i-ring.c"
> -
> -struct FTSENT_wrapper
> -{
> - FTSOBJ *fts_fts; /* the file hierarchy itself */
> - DIR *fts_dirp; /* Dir pointer for any directory containing
> - more entries than we read at one time. */
> - struct STRUCT_STAT fts_stat;
> -
> - FTSENTRY ent;
> -};
> -
> -/* glibc historicaly defines the FTS::fts_compar as having 'void *', while the
> - fts_open has a function point using 'FTSENT **' as argument. */
> -# define FTS_COMPAR_CAST(__fn) ((int (*) (void const *, void const *))__fn)
> -
> -# define FTSENT_WRAPPER(p) \
> - ((struct FTSENT_wrapper *) ((char *)(p) - offsetof(struct FTSENT_wrapper, ent)))
> -#endif
> -#define FTSENT_FTS(p) (FTSENT_WRAPPER(p)->fts_fts)
> -#define FTSENT_DIRP(p) (FTSENT_WRAPPER(p)->fts_dirp)
> -
> -#include "flexmember.h"
> -
> -#include <dirent.h>
> -#ifndef _D_EXACT_NAMLEN
> -# define _D_EXACT_NAMLEN(dirent) strlen ((dirent)->d_name)
> -#endif
> -
> -#if HAVE_STRUCT_DIRENT_D_TYPE
> -/* True if the type of the directory entry D is known. */
> -# define DT_IS_KNOWN(d) ((d)->d_type != DT_UNKNOWN)
> -/* True if the type of the directory entry D must be T. */
> -# define DT_MUST_BE(d, t) ((d)->d_type == (t))
> -# define D_TYPE(d) ((d)->d_type)
> -#else
> -# define DT_IS_KNOWN(d) false
> -# define DT_MUST_BE(d, t) false
> -# define D_TYPE(d) DT_UNKNOWN
> -
> -# undef DT_UNKNOWN
> -# define DT_UNKNOWN 0
> -
> -/* Any nonzero values will do here, so long as they're distinct.
> - Undef any existing macros out of the way. */
> -# undef DT_BLK
> -# undef DT_CHR
> -# undef DT_DIR
> -# undef DT_FIFO
> -# undef DT_LNK
> -# undef DT_REG
> -# undef DT_SOCK
> -# define DT_BLK 1
> -# define DT_CHR 2
> -# define DT_DIR 3
> -# define DT_FIFO 4
> -# define DT_LNK 5
> -# define DT_REG 6
> -# define DT_SOCK 7
> -#endif
> -
> -#ifndef S_IFBLK
> -# define S_IFBLK 0
> -#endif
> -#ifndef S_IFLNK
> -# define S_IFLNK 0
> -#endif
> -#ifndef S_IFSOCK
> -# define S_IFSOCK 0
> -#endif
> -
> -enum
> -{
> - NOT_AN_INODE_NUMBER = 0
> -};
> -
> -#ifdef D_INO_IN_DIRENT
> -# define D_INO(dp) (dp)->d_ino
> -#else
> -/* Some systems don't have inodes, so fake them to avoid lots of ifdefs. */
> -# define D_INO(dp) NOT_AN_INODE_NUMBER
> -#endif
> -
> -/* If possible (see max_entries, below), read no more than this many directory
> - entries at a time. Without this limit (i.e., when using non-NULL
> - fts_compar), processing a directory with 4,000,000 entries requires ~1GiB
> - of memory, and handling 64M entries would require 16GiB of memory. */
> -#ifndef FTS_MAX_READDIR_ENTRIES
> -# define FTS_MAX_READDIR_ENTRIES 100000
> -#endif
> -
> -/* If there are more than this many entries in a directory,
> - and the conditions mentioned below are satisfied, then sort
> - the entries on inode number before any further processing. */
> -#ifndef FTS_INODE_SORT_DIR_ENTRIES_THRESHOLD
> -# define FTS_INODE_SORT_DIR_ENTRIES_THRESHOLD 10000
> -#endif
> -
> -enum
> -{
> - _FTS_INODE_SORT_DIR_ENTRIES_THRESHOLD = FTS_INODE_SORT_DIR_ENTRIES_THRESHOLD
> -};
> -
> -enum Fts_stat
> -{
> - FTS_NO_STAT_REQUIRED = 1,
> - FTS_STAT_REQUIRED = 2
> -};
> -
> -#ifndef __set_errno
> -# define __set_errno(Val) errno = (Val)
> -#endif
> -
> -/* If this host provides the openat function, then we can avoid
> - attempting to open "." in some initialization code below. */
> -#ifdef HAVE_OPENAT
> -# define HAVE_OPENAT_SUPPORT 1
> -#else
> -# define HAVE_OPENAT_SUPPORT 0
> -#endif
> -
> -#ifdef NDEBUG
> -# define fts_assert(expr) ((void) (0 && (expr)))
> -#else
> -# define fts_assert(expr) \
> - do \
> - { \
> - if (!(expr)) \
> - abort (); \
> - } \
> - while (false)
> -#endif
> -
> -static FTSENTRY *fts_alloc (FTSOBJ *, const char *, size_t);
> -static FTSENTRY *fts_build (FTSOBJ *, int);
> -static void fts_lfree (FTSENTRY *);
> -static void fts_load (FTSOBJ *, FTSENTRY *);
> -static size_t fts_maxarglen (char * const *);
> -static void fts_padjust (FTSOBJ *, FTSENTRY *);
> -static bool fts_palloc (FTSOBJ *, size_t);
> -static FTSENTRY *fts_sort (FTSOBJ *, FTSENTRY *, size_t);
> -static unsigned short int fts_stat (FTSOBJ *, FTSENTRY *, bool);
> -static int fts_safe_changedir (FTSOBJ *, FTSENTRY *, int, const char *);
> -
> -#include "fts-cycle.c"
> -
> -#ifndef MAX
> -# define MAX(a,b) ((a) > (b) ? (a) : (b))
> -#endif
> -
> -#ifndef SIZE_MAX
> -# define SIZE_MAX ((size_t) -1)
> -#endif
> -
> -#define ISDOT(a) (a[0] == '.' && (!a[1] || (a[1] == '.' && !a[2])))
> -
> -#define CLR(opt) (sp->fts_options &= ~(opt))
> -#define ISSET(opt) ((sp->fts_options & (opt)) != 0)
> -#define SET(opt) (sp->fts_options |= (opt))
> -
> -/* FIXME: FTS_NOCHDIR is now misnamed.
> - Call it FTS_USE_FULL_RELATIVE_FILE_NAMES instead. */
> -#define FCHDIR(sp, fd) \
> - (!ISSET(FTS_NOCHDIR) && (ISSET(FTS_CWDFD) \
> - ? (cwd_advance_fd ((sp), (fd), true), 0) \
> - : fchdir (fd)))
> -
> -
> -/* fts_build flags */
> -/* FIXME: make this an enum */
> -#define BCHILD 1 /* fts_children */
> -#define BNAMES 2 /* fts_children, names only */
> -#define BREAD 3 /* fts_read */
> -
> -#if GNULIB_FTS_DEBUG
> -# include <inttypes.h>
> -# include <stdio.h>
> -bool fts_debug = false;
> -# define Dprintf(x) do { if (fts_debug) printf x; } while (false)
> -static void fd_ring_check (FTSOBJ const *);
> -static void fd_ring_print (FTSOBJ const *, FILE *, char const *);
> -#else
> -# define Dprintf(x)
> -# define fd_ring_check(x)
> -# define fd_ring_print(a, b, c)
> -#endif
> -
> -#define LEAVE_DIR(Fts, Ent, Tag) \
> - do \
> - { \
> - Dprintf ((" %s-leaving: %s\n", Tag, (Ent)->fts_path)); \
> - leave_dir (Fts, Ent); \
> - fd_ring_check (Fts); \
> - } \
> - while (false)
> -
> -static void
> -fd_ring_clear (I_ring *fd_ring)
> -{
> - while ( ! i_ring_empty (fd_ring))
> - {
> - int fd = i_ring_pop (fd_ring);
> - if (0 <= fd)
> - close (fd);
> - }
> -}
> -
> -/* Overload the fts_statp->st_size member (otherwise unused, when
> - fts_info is FTS_NSOK) to indicate whether fts_read should stat
> - this entry or not. */
> -static void
> -fts_set_stat_required (FTSENTRY *p, bool required)
> -{
> - fts_assert (p->fts_info == FTS_NSOK);
> - p->fts_statp->st_size = (required
> - ? FTS_STAT_REQUIRED
> - : FTS_NO_STAT_REQUIRED);
> -}
> -
> -/* Virtual fchdir. Advance SP's working directory file descriptor,
> - SP->fts_cwd_fd, to FD, and push the previous value onto the fd_ring.
> - CHDIR_DOWN_ONE is true if FD corresponds to an entry in the directory
> - open on sp->fts_cwd_fd; i.e., to move the working directory one level
> - down. */
> -static void
> -internal_function
> -cwd_advance_fd (FTSOBJ *sp, int fd, bool chdir_down_one)
> -{
> - int old = sp->fts_cwd_fd;
> - fts_assert (old != fd || old == AT_FDCWD);
> -
> - if (chdir_down_one)
> - {
> - /* Push "old" onto the ring.
> - If the displaced file descriptor is non-negative, close it. */
> - int prev_fd_in_slot = i_ring_push (&sp->fts_fd_ring, old);
> - fd_ring_print (sp, stderr, "post-push");
> - if (0 <= prev_fd_in_slot)
> - close (prev_fd_in_slot); /* ignore any close failure */
> - }
> - else if ( ! ISSET (FTS_NOCHDIR))
> - {
> - if (0 <= old)
> - close (old); /* ignore any close failure */
> - }
> -
> - sp->fts_cwd_fd = fd;
> -}
> -
> -/* Restore the initial, pre-traversal, "working directory".
> - In FTS_CWDFD mode, we merely call cwd_advance_fd, otherwise,
> - we may actually change the working directory.
> - Return 0 upon success. Upon failure, set errno and return nonzero. */
> -static int
> -restore_initial_cwd (FTSOBJ *sp)
> -{
> - int fail = FCHDIR (sp, ISSET (FTS_CWDFD) ? AT_FDCWD : sp->fts_rfd);
> - fd_ring_clear (&(sp->fts_fd_ring));
> - return fail;
> -}
> -
> -/* Open the directory DIR if possible, and return a file
> - descriptor. Return -1 and set errno on failure. It doesn't matter
> - whether the file descriptor has read or write access. */
> -
> -static int
> -internal_function
> -diropen (FTSOBJ const *sp, char const *dir)
> -{
> - int open_flags = (O_SEARCH | O_CLOEXEC | O_DIRECTORY | O_NOCTTY | O_NONBLOCK
> - | (ISSET (FTS_PHYSICAL) ? O_NOFOLLOW : 0));
> -
> - int fd = (ISSET (FTS_CWDFD)
> - ? openat (sp->fts_cwd_fd, dir, open_flags)
> - : open (dir, open_flags));
> - return fd;
> -}
> -
> -FTSOBJ *
> -FTS_OPEN (char * const *argv,
> - register int options,
> - int (*compar) (const FTSENTRY **, const FTSENTRY **))
> -{
> - /* Options check: glibc added other flags after FTS_NAMEONLY and
> - FTS_STOP, and they are assumed to be private. */
> - if (options & ~FTS_OPTIONMASK
> -#if _LIBC
> - || options & (FTS_NAMEONLY | FTS_STOP)
> -#endif
> - ) {
> - __set_errno (EINVAL);
> - return (NULL);
> - }
> - if ((options & FTS_NOCHDIR) && (options & FTS_CWDFD)) {
> - __set_errno (EINVAL);
> - return (NULL);
> - }
> -#if !_LIBC
> - if ( ! (options & (FTS_LOGICAL | FTS_PHYSICAL))) {
> - __set_errno (EINVAL);
> - return (NULL);
> - }
> -#else
> - /* glibc historically falls to FTS_PHYSICAL if no FTS_PHYSICAL or
> - FTS_LOGICAL is specified. */
> - if (! (options & (FTS_PHYSICAL | FTS_LOGICAL)))
> - options |= FTS_PHYSICAL;
> -#endif
> -
> - /* Allocate/initialize the stream */
> - register FTSOBJ *sp = calloc (1, sizeof *sp);
> - if (sp == NULL)
> - return (NULL);
> - sp->fts_compar = FTS_COMPAR_CAST(compar);
> - sp->fts_options = options;
> -
> - /* Logical walks turn on NOCHDIR; symbolic links are too hard. */
> - if (ISSET(FTS_LOGICAL)) {
> - SET(FTS_NOCHDIR);
> - CLR(FTS_CWDFD);
> - }
> -
> - /* Initialize fts_cwd_fd. */
> - sp->fts_cwd_fd = AT_FDCWD;
> - if ( ISSET(FTS_CWDFD) && ! HAVE_OPENAT_SUPPORT)
> - {
> - /* While it isn't technically necessary to open "." this
> - early, doing it here saves us the trouble of ensuring
> - later (where it'd be messier) that "." can in fact
> - be opened. If not, revert to FTS_NOCHDIR mode. */
> - int fd = open (".", O_SEARCH | O_CLOEXEC);
> - if (fd < 0)
> - {
> - /* Even if "." is unreadable, don't revert to FTS_NOCHDIR mode
> - on systems like Linux+PROC_FS, where our openat emulation
> - is good enough. Note: on a system that emulates
> - openat via /proc, this technique can still fail, but
> - only in extreme conditions, e.g., when the working
> - directory cannot be saved (i.e. save_cwd fails) --
> - and that happens on Linux only when "." is unreadable
> - and the CWD would be longer than PATH_MAX.
> - FIXME: once Linux kernel openat support is well established,
> - replace the above open call and this entire if/else block
> - with the body of the if-block below. */
> - if ( openat_needs_fchdir ())
> - {
> - SET(FTS_NOCHDIR);
> - CLR(FTS_CWDFD);
> - }
> - }
> - else
> - {
> - close (fd);
> - }
> - }
> -
> - /*
> - * Start out with 1K of file name space, and enough, in any case,
> - * to hold the user's file names.
> - */
> -#ifndef MAXPATHLEN
> -# define MAXPATHLEN 1024
> -#endif
> - {
> - size_t maxarglen = fts_maxarglen(argv);
> - if (! fts_palloc(sp, MAX(maxarglen, MAXPATHLEN)))
> - goto mem1;
> - }
> -
> - /* Allocate/initialize root's parent. */
> - FTSENTRY *parent = NULL;
> - if (*argv != NULL) {
> - if ((parent = fts_alloc(sp, "", 0)) == NULL)
> - goto mem2;
> - parent->fts_level = FTS_ROOTPARENTLEVEL;
> - }
> -
> - /* The classic fts implementation would call fts_stat with
> - a new entry for each iteration of the loop below.
> - If the comparison function is not specified or if the
> - FTS_DEFER_STAT option is in effect, don't stat any entry
> - in this loop. This is an attempt to minimize the interval
> - between the initial stat/lstat/fstatat and the point at which
> - a directory argument is first opened. This matters for any
> - directory command line argument that resides on a file system
> - without genuine i-nodes. If you specify FTS_DEFER_STAT along
> - with a comparison function, that function must not access any
> - data via the fts_statp pointer. */
> - bool defer_stat = (compar == NULL || ISSET(FTS_DEFER_STAT));
> -
> - /* Allocate/initialize root(s). */
> - register FTSENTRY *root;
> - register size_t nitems;
> - FTSENTRY *tmp = NULL; /* pacify gcc */
> - for (root = NULL, nitems = 0; *argv != NULL; ++argv, ++nitems) {
> - /* *Do* allow zero-length file names. */
> - size_t len = strlen(*argv);
> -
> - if ( ! (options & FTS_VERBATIM))
> - {
> - /* If there are two or more trailing slashes, trim all but one,
> - but don't change "//" to "/", and do map "///" to "/". */
> - char const *v = *argv;
> - if (2 < len && v[len - 1] == '/')
> - while (1 < len && v[len - 2] == '/')
> - --len;
> - }
> -
> - register FTSENTRY *p = fts_alloc(sp, *argv, len);
> - if (p == NULL)
> - goto mem3;
> - p->fts_level = FTS_ROOTLEVEL;
> - p->fts_parent = parent;
> - p->fts_accpath = p->fts_name;
> - /* Even when defer_stat is true, be sure to stat the first
> - command line argument, since fts_read (at least with
> - FTS_XDEV) requires that. */
> - if (defer_stat && root != NULL) {
> - p->fts_info = FTS_NSOK;
> - fts_set_stat_required(p, true);
> - } else {
> - p->fts_info = fts_stat(sp, p, false);
> - }
> -
> - /*
> - * If comparison routine supplied, traverse in sorted
> - * order; otherwise traverse in the order specified.
> - */
> - if (compar) {
> - p->fts_link = root;
> - root = p;
> - } else {
> - p->fts_link = NULL;
> - if (root == NULL)
> - tmp = root = p;
> - else {
> - tmp->fts_link = p;
> - tmp = p;
> - }
> - }
> - }
> - if (compar && nitems > 1)
> - root = fts_sort(sp, root, nitems);
> -
> - /*
> - * Allocate a dummy pointer and make fts_read think that we've just
> - * finished the node before the root(s); set p->fts_info to FTS_INIT
> - * so that everything about the "current" node is ignored.
> - */
> - if ((sp->fts_cur = fts_alloc(sp, "", 0)) == NULL)
> - goto mem3;
> - sp->fts_cur->fts_link = root;
> - sp->fts_cur->fts_info = FTS_INIT;
> - sp->fts_cur->fts_level = 1;
> - if (! setup_dir (sp))
> - goto mem3;
> -
> - /*
> - * If using chdir(2), grab a file descriptor pointing to dot to ensure
> - * that we can get back here; this could be avoided for some file names,
> - * but almost certainly not worth the effort. Slashes, symbolic links,
> - * and ".." are all fairly nasty problems. Note, if we can't get the
> - * descriptor we run anyway, just more slowly.
> - */
> - if (!ISSET(FTS_NOCHDIR) && !ISSET(FTS_CWDFD)
> - && (sp->fts_rfd = diropen (sp, ".")) < 0)
> - SET(FTS_NOCHDIR);
> -
> - i_ring_init (&sp->fts_fd_ring, -1);
> - return (sp);
> -
> -mem3: fts_lfree(root);
> - free(FTSENT_WRAPPER(parent));
> -mem2: free(sp->fts_path);
> -mem1: free(sp);
> - return (NULL);
> -}
> -
> -static void
> -internal_function
> -fts_load (FTSOBJ *sp, register FTSENTRY *p)
> -{
> - /*
> - * Load the stream structure for the next traversal. Since we don't
> - * actually enter the directory until after the preorder visit, set
> - * the fts_accpath field specially so the chdir gets done to the right
> - * place and the user can access the first node. From fts_open it's
> - * known that the file name will fit.
> - */
> - register size_t len = p->fts_pathlen = p->fts_namelen;
> - memmove(sp->fts_path, p->fts_name, len + 1);
> - register char *cp = strrchr(p->fts_name, '/');
> - if (cp && (cp != p->fts_name || cp[1])) {
> - len = strlen(++cp);
> - memmove(p->fts_name, cp, len + 1);
> - p->fts_namelen = len;
> - }
> - p->fts_accpath = p->fts_path = sp->fts_path;
> -}
> -
> -int
> -FTS_CLOSE (FTSOBJ *sp)
> -{
> - /*
> - * This still works if we haven't read anything -- the dummy structure
> - * points to the root list, so we step through to the end of the root
> - * list which has a valid parent pointer.
> - */
> - if (sp->fts_cur) {
> - register FTSENTRY *p;
> - for (p = sp->fts_cur; p->fts_level >= FTS_ROOTLEVEL;) {
> - register FTSENTRY *freep = p;
> - p = p->fts_link != NULL ? p->fts_link : p->fts_parent;
> - free(FTSENT_WRAPPER(freep));
> - }
> - free(FTSENT_WRAPPER(p));
> - }
> -
> - /* Free up child linked list, sort array, file name buffer. */
> - if (sp->fts_child)
> - fts_lfree(sp->fts_child);
> - free(sp->fts_array);
> - free(sp->fts_path);
> -
> - int saved_errno = 0;
> - if (ISSET(FTS_CWDFD))
> - {
> - if (0 <= sp->fts_cwd_fd)
> - if (close (sp->fts_cwd_fd))
> - saved_errno = errno;
> - }
> - else if (!ISSET(FTS_NOCHDIR))
> - {
> - /* Return to original directory, save errno if necessary. */
> - if (fchdir(sp->fts_rfd))
> - saved_errno = errno;
> -
> - /* If close fails, record errno only if saved_errno is zero,
> - so that we report the probably-more-meaningful fchdir errno. */
> - if (close (sp->fts_rfd))
> - if (saved_errno == 0)
> - saved_errno = errno;
> - }
> -
> - fd_ring_clear (&sp->fts_fd_ring);
> -
> - if (sp->fts_leaf_optimization_works_ht)
> - hash_free (sp->fts_leaf_optimization_works_ht);
> -
> - free_dir (sp);
> -
> - /* Free up the stream pointer. */
> - free(sp);
> -
> - /* Set errno and return. */
> - if (saved_errno) {
> - __set_errno (saved_errno);
> - return (-1);
> - }
> -
> - return (0);
> -}
> -
> -/* Minimum link count of a traditional Unix directory. When leaf
> - optimization is OK and a directory's st_nlink == MIN_DIR_NLINK,
> - then the directory has no subdirectories. */
> -enum { MIN_DIR_NLINK = 2 };
> -
> -/* Whether leaf optimization is OK for a directory. */
> -enum leaf_optimization
> - {
> - /* st_nlink is not reliable for this directory's subdirectories. */
> - NO_LEAF_OPTIMIZATION,
> -
> - /* st_nlink == 2 means the directory lacks subdirectories. */
> - OK_LEAF_OPTIMIZATION
> - };
> -
> -#if (defined __linux__ || defined __ANDROID__) \
> - && HAVE_SYS_VFS_H && HAVE_FSTATFS && HAVE_STRUCT_STATFS_F_TYPE
> -
> -# include <sys/vfs.h>
> -
> -/* Linux-specific constants from coreutils' src/fs.h */
> -# define S_MAGIC_AFS 0x5346414F
> -# define S_MAGIC_CIFS 0xFF534D42
> -# define S_MAGIC_LUSTRE 0x0BD00BD0
> -# define S_MAGIC_NFS 0x6969
> -# define S_MAGIC_PROC 0x9FA0
> -# define S_MAGIC_TMPFS 0x1021994
> -
> -# ifdef HAVE___FSWORD_T
> -typedef __fsword_t fsword;
> -# else
> -typedef long int fsword;
> -# endif
> -
> -/* Map a stat.st_dev number to a file system type number f_ftype. */
> -struct dev_type
> -{
> - dev_t st_dev;
> - fsword f_type;
> -};
> -
> -/* Use a tiny initial size. If a traversal encounters more than
> - a few devices, the cost of growing/rehashing this table will be
> - rendered negligible by the number of inodes processed. */
> -enum { DEV_TYPE_HT_INITIAL_SIZE = 13 };
> -
> -static size_t
> -dev_type_hash (void const *x, size_t table_size)
> -{
> - struct dev_type const *ax = x;
> - uintmax_t dev = ax->st_dev;
> - return dev % table_size;
> -}
> -
> -static bool
> -dev_type_compare (void const *x, void const *y)
> -{
> - struct dev_type const *ax = x;
> - struct dev_type const *ay = y;
> - return ax->st_dev == ay->st_dev;
> -}
> -
> -/* Return the file system type of P with file descriptor FD, or 0 if not known.
> - If FD is negative, P's file descriptor is unavailable.
> - Try to cache known values. */
> -
> -static fsword
> -filesystem_type (FTSENTRY const *p, int fd)
> -{
> - FTSOBJ *sp = FTSENT_FTS(p);
> -
> - /* If we're not in CWDFD mode, don't bother with this optimization,
> - since the caller is not serious about performance. */
> - if (!ISSET (FTS_CWDFD))
> - return 0;
> -
> - Hash_table *h = sp->fts_leaf_optimization_works_ht;
> - if (! h)
> - h = sp->fts_leaf_optimization_works_ht
> - = hash_initialize (DEV_TYPE_HT_INITIAL_SIZE, NULL, dev_type_hash,
> - dev_type_compare, free);
> -
> - if (h)
> - {
> - struct dev_type tmp;
> - tmp.st_dev = p->fts_statp->st_dev;
> - struct dev_type *ent = hash_lookup (h, &tmp);
> - if (ent)
> - return ent->f_type;
> - }
> -
> - /* Look-up failed. Query directly and cache the result. */
> - struct STRUCT_STATFS fs_buf;
> - if (fd < 0 || FSTATFS (fd, &fs_buf) != 0)
> - return 0;
> -
> - if (h)
> - {
> - struct dev_type *t2 = malloc (sizeof *t2);
> - if (t2)
> - {
> - t2->st_dev = p->fts_statp->st_dev;
> - t2->f_type = fs_buf.f_type;
> -
> - struct dev_type *ent = hash_insert (h, t2);
> - if (ent)
> - fts_assert (ent == t2);
> - else
> - free (t2);
> - }
> - }
> -
> - return fs_buf.f_type;
> -}
> -
> -/* Return true if sorting dirents on inode numbers is known to improve
> - traversal performance for the directory P with descriptor DIR_FD.
> - Return false otherwise. When in doubt, return true.
> - DIR_FD is negative if unavailable. */
> -static bool
> -dirent_inode_sort_may_be_useful (FTSENTRY const *p, int dir_fd)
> -{
> - /* Skip the sort only if we can determine efficiently
> - that skipping it is the right thing to do.
> - The cost of performing an unnecessary sort is negligible,
> - while the cost of *not* performing it can be O(N^2) with
> - a very large constant. */
> -
> - switch (filesystem_type (p, dir_fd))
> - {
> - case S_MAGIC_LUSTRE:
> - /* On Lustre, sorting directory entries interferes with its ability to
> - prefetch file metadata (via statahead). This would make a command
> - like 'du' around 9 times slower. See
> - <https://bugs.gnu.org/80106>. */
> - case S_MAGIC_CIFS:
> - case S_MAGIC_NFS:
> - case S_MAGIC_TMPFS:
> - /* On a file system of any of these types, sorting
> - is unnecessary, and hence wasteful. */
> - return false;
> -
> - default:
> - return true;
> - }
> -}
> -
> -/* Given an FTS entry P for a directory with descriptor DIR_FD,
> - return whether it is valid to apply leaf optimization.
> - The optimization is valid if a directory's st_nlink value equal
> - to MIN_DIR_NLINK means the directory has no subdirectories.
> - DIR_FD is negative if unavailable. */
> -static enum leaf_optimization
> -leaf_optimization (FTSENTRY const *p, int dir_fd)
> -{
> - switch (filesystem_type (p, dir_fd))
> - {
> - case 0:
> - /* Leaf optimization is unsafe if the file system type is unknown. */
> - FALLTHROUGH;
> - case S_MAGIC_AFS:
> - /* Although AFS mount points are not counted in st_nlink, they
> - act like directories. See <https://bugs.debian.org/143111>. */
> - FALLTHROUGH;
> - case S_MAGIC_CIFS:
> - /* Leaf optimization causes 'find' to abort. See
> - <https://lists.gnu.org/r/bug-gnulib/2018-04/msg00015.html>. */
> - FALLTHROUGH;
> - case S_MAGIC_NFS:
> - /* NFS provides usable dirent.d_type but not necessarily for all entries
> - of large directories, so as per <https://bugzilla.redhat.com/1252549>
> - NFS should return true. However st_nlink values are not accurate on
> - all implementations as per <https://bugzilla.redhat.com/1299169>. */
> - FALLTHROUGH;
> - case S_MAGIC_PROC:
> - /* Per <https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=143111> /proc
> - may have bogus stat.st_nlink values. */
> - return NO_LEAF_OPTIMIZATION;
> -
> - default:
> - return OK_LEAF_OPTIMIZATION;
> - }
> -}
> -
> -#else
> -static bool
> -dirent_inode_sort_may_be_useful (_GL_UNUSED FTSENTRY const *p,
> - _GL_UNUSED int dir_fd)
> -{
> - return true;
> -}
> -static enum leaf_optimization
> -leaf_optimization (_GL_UNUSED FTSENTRY const *p, _GL_UNUSED int dir_fd)
> -{
> - return NO_LEAF_OPTIMIZATION;
> -}
> -#endif
> -
> -/*
> - * Special case of "/" at the end of the file name so that slashes aren't
> - * appended which would cause file names to be written as "....//foo".
> - */
> -#define NAPPEND(p) \
> - (p->fts_path[p->fts_pathlen - 1] == '/' \
> - ? p->fts_pathlen - 1 : p->fts_pathlen)
> -
> -FTSENTRY *
> -FTS_READ (FTSOBJ *sp)
> -{
> - /* If finished or unrecoverable error, return NULL. */
> - if (sp->fts_cur == NULL || ISSET(FTS_STOP))
> - return (NULL);
> -
> - /* Set current node pointer. */
> - register FTSENTRY *p = sp->fts_cur;
> -
> - /* Save and zero out user instructions. */
> - register unsigned short int instr = p->fts_instr;
> - p->fts_instr = FTS_NOINSTR;
> -
> - /* Any type of file may be re-visited; re-stat and re-turn. */
> - if (instr == FTS_AGAIN) {
> - p->fts_info = fts_stat(sp, p, false);
> - return (p);
> - }
> - Dprintf (("fts_read: p=%s\n",
> - p->fts_info == FTS_INIT ? "" : p->fts_path));
> -
> - /*
> - * Following a symlink -- SLNONE test allows application to see
> - * SLNONE and recover. If indirecting through a symlink, have
> - * keep a pointer to current location. If unable to get that
> - * pointer, follow fails.
> - */
> - if (instr == FTS_FOLLOW &&
> - (p->fts_info == FTS_SL || p->fts_info == FTS_SLNONE)) {
> - p->fts_info = fts_stat(sp, p, true);
> - if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) {
> - if ((p->fts_symfd = diropen (sp, ".")) < 0) {
> - p->fts_errno = errno;
> - p->fts_info = FTS_ERR;
> - } else
> - p->fts_flags |= FTS_SYMFOLLOW;
> - }
> - goto check_for_dir;
> - }
> -
> - /* Directory in pre-order. */
> - if (p->fts_info == FTS_D) {
> - /* If skipped or crossed mount point, do post-order visit. */
> - if (instr == FTS_SKIP ||
> - (ISSET(FTS_XDEV) && p->fts_statp->st_dev != sp->fts_dev)) {
> - if (p->fts_flags & FTS_SYMFOLLOW)
> - (void)close(p->fts_symfd);
> - if (sp->fts_child) {
> - fts_lfree(sp->fts_child);
> - sp->fts_child = NULL;
> - }
> - p->fts_info = FTS_DP;
> - LEAVE_DIR (sp, p, "1");
> - return (p);
> - }
> -
> - /* Rebuild if only read the names and now traversing. */
> - if (sp->fts_child != NULL && ISSET(FTS_NAMEONLY)) {
> - CLR(FTS_NAMEONLY);
> - fts_lfree(sp->fts_child);
> - sp->fts_child = NULL;
> - }
> -
> - /*
> - * Cd to the subdirectory.
> - *
> - * If have already read and now fail to chdir, whack the list
> - * to make the names come out right, and set the parent errno
> - * so the application will eventually get an error condition.
> - * Set the FTS_DONTCHDIR flag so that when we logically change
> - * directories back to the parent we don't do a chdir.
> - *
> - * If haven't read do so. If the read fails, fts_build sets
> - * FTS_STOP or the fts_info field of the node.
> - */
> - if (sp->fts_child != NULL) {
> - if (fts_safe_changedir(sp, p, -1, p->fts_accpath)) {
> - p->fts_errno = errno;
> - p->fts_flags |= FTS_DONTCHDIR;
> - for (p = sp->fts_child; p != NULL;
> - p = p->fts_link)
> - p->fts_accpath =
> - p->fts_parent->fts_accpath;
> - }
> - } else if ((sp->fts_child = fts_build(sp, BREAD)) == NULL) {
> - if (ISSET(FTS_STOP))
> - return (NULL);
> - /* If fts_build's call to fts_safe_changedir failed
> - because it was not able to fchdir into a
> - subdirectory, tell the caller. */
> - if (p->fts_errno && p->fts_info != FTS_DNR)
> - p->fts_info = FTS_ERR;
> - LEAVE_DIR (sp, p, "2");
> - return (p);
> - }
> - p = sp->fts_child;
> - sp->fts_child = NULL;
> - goto name;
> - }
> -
> - /* Move to the next node on this level. */
> -next: ;
> - register FTSENTRY *tmp = p;
> -
> - /* If we have so many directory entries that we're reading them
> - in batches, and we've reached the end of the current batch,
> - read in a new batch. */
> - if (p->fts_link == NULL && FTSENT_DIRP(p->fts_parent))
> - {
> - p = tmp->fts_parent;
> - sp->fts_cur = p;
> - sp->fts_path[p->fts_pathlen] = '\0';
> -
> - if ((p = fts_build (sp, BREAD)) == NULL)
> - {
> - if (ISSET(FTS_STOP))
> - return NULL;
> - goto cd_dot_dot;
> - }
> -
> - free(FTSENT_WRAPPER(tmp));
> - goto name;
> - }
> -
> - if ((p = p->fts_link) != NULL) {
> - sp->fts_cur = p;
> - free(FTSENT_WRAPPER(tmp));
> -
> - /*
> - * If reached the top, return to the original directory (or
> - * the root of the tree), and load the file names for the next
> - * root.
> - */
> - if (p->fts_level == FTS_ROOTLEVEL) {
> - if (restore_initial_cwd(sp)) {
> - SET(FTS_STOP);
> - return (NULL);
> - }
> - free_dir(sp);
> - fts_load(sp, p);
> - if (! setup_dir(sp)) {
> - free_dir(sp);
> - return (NULL);
> - }
> - goto check_for_dir;
> - }
> -
> - /*
> - * User may have called fts_set on the node. If skipped,
> - * ignore. If followed, get a file descriptor so we can
> - * get back if necessary.
> - */
> - if (p->fts_instr == FTS_SKIP)
> - goto next;
> - if (p->fts_instr == FTS_FOLLOW) {
> - p->fts_info = fts_stat(sp, p, true);
> - if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) {
> - if ((p->fts_symfd = diropen (sp, ".")) < 0) {
> - p->fts_errno = errno;
> - p->fts_info = FTS_ERR;
> - } else
> - p->fts_flags |= FTS_SYMFOLLOW;
> - }
> - p->fts_instr = FTS_NOINSTR;
> - }
> -
> -name: {
> - register char *t = sp->fts_path + NAPPEND(p->fts_parent);
> - *t++ = '/';
> - memmove(t, p->fts_name, p->fts_namelen + 1);
> - }
> -check_for_dir:
> - sp->fts_cur = p;
> - if (p->fts_info == FTS_NSOK)
> - {
> - if (p->fts_statp->st_size == FTS_STAT_REQUIRED)
> - p->fts_info = fts_stat(sp, p, false);
> - else
> - fts_assert (p->fts_statp->st_size == FTS_NO_STAT_REQUIRED);
> - }
> -
> - /* Skip files with different device numbers when FTS_MOUNT
> - is set. */
> - if (ISSET (FTS_MOUNT) && p->fts_info != FTS_NS &&
> - p->fts_level != FTS_ROOTLEVEL &&
> - p->fts_statp->st_dev != sp->fts_dev)
> - goto next;
> -
> - if (p->fts_info == FTS_D)
> - {
> - /* Now that P->fts_statp is guaranteed to be valid, if
> - this is a command-line directory, record its device
> - number, to be used for FTS_MOUNT and FTS_XDEV. */
> - if (p->fts_level == FTS_ROOTLEVEL)
> - sp->fts_dev = p->fts_statp->st_dev;
> - Dprintf ((" entering: %s\n", p->fts_path));
> - if (! enter_dir (sp, p))
> - return NULL;
> - }
> - return p;
> - }
> -cd_dot_dot:
> -
> - /* Move up to the parent node. */
> - p = tmp->fts_parent;
> - sp->fts_cur = p;
> - free(FTSENT_WRAPPER(tmp));
> -
> - if (p->fts_level == FTS_ROOTPARENTLEVEL) {
> - /*
> - * Done; free everything up and set errno to 0 so the user
> - * can distinguish between error and EOF.
> - */
> - free(FTSENT_WRAPPER(p));
> - __set_errno (0);
> - return (sp->fts_cur = NULL);
> - }
> -
> - fts_assert (p->fts_info != FTS_NSOK);
> -
> - /* NUL terminate the file name. */
> - sp->fts_path[p->fts_pathlen] = '\0';
> -
> - /*
> - * Return to the parent directory. If at a root node, restore
> - * the initial working directory. If we came through a symlink,
> - * go back through the file descriptor. Otherwise, move up
> - * one level, via "..".
> - */
> - if (p->fts_level == FTS_ROOTLEVEL) {
> - if (restore_initial_cwd(sp)) {
> - p->fts_errno = errno;
> - SET(FTS_STOP);
> - }
> - } else if (p->fts_flags & FTS_SYMFOLLOW) {
> - if (FCHDIR(sp, p->fts_symfd)) {
> - p->fts_errno = errno;
> - SET(FTS_STOP);
> - }
> - (void)close(p->fts_symfd);
> - } else if (!(p->fts_flags & FTS_DONTCHDIR) &&
> - fts_safe_changedir(sp, p->fts_parent, -1, "..")) {
> - p->fts_errno = errno;
> - SET(FTS_STOP);
> - }
> -
> - /* If the directory causes a cycle, preserve the FTS_DC flag and keep
> - the corresponding dev/ino pair in the hash table. It is going to be
> - removed when leaving the original directory. */
> - if (p->fts_info != FTS_DC) {
> - p->fts_info = p->fts_errno ? FTS_ERR : FTS_DP;
> - if (p->fts_errno == 0)
> - LEAVE_DIR (sp, p, "3");
> - }
> - return ISSET(FTS_STOP) ? NULL : p;
> -}
> -
> -/*
> - * Fts_set takes the stream as an argument although it's not used in this
> - * implementation; it would be necessary if anyone wanted to add global
> - * semantics to fts using fts_set. An error return is allowed for similar
> - * reasons.
> - */
> -/* ARGSUSED */
> -int
> -FTS_SET (_GL_UNUSED FTSOBJ *sp, FTSENTRY *p, int instr)
> -{
> - if (instr != 0 && instr != FTS_AGAIN && instr != FTS_FOLLOW &&
> - instr != FTS_NOINSTR && instr != FTS_SKIP) {
> - __set_errno (EINVAL);
> - return (1);
> - }
> - p->fts_instr = instr;
> - return (0);
> -}
> -
> -FTSENTRY *
> -FTS_CHILDREN (FTSOBJ *sp, int instr)
> -{
> - if (instr != 0 && instr != FTS_NAMEONLY) {
> - __set_errno (EINVAL);
> - return (NULL);
> - }
> -
> - /* Set current node pointer. */
> - register FTSENTRY *p = sp->fts_cur;
> -
> - /*
> - * Errno set to 0 so user can distinguish empty directory from
> - * an error.
> - */
> - __set_errno (0);
> -
> - /* Fatal errors stop here. */
> - if (ISSET(FTS_STOP))
> - return (NULL);
> -
> - /* Return logical hierarchy of user's arguments. */
> - if (p->fts_info == FTS_INIT)
> - return (p->fts_link);
> -
> - /*
> - * If not a directory being visited in pre-order, stop here. Could
> - * allow FTS_DNR, assuming the user has fixed the problem, but the
> - * same effect is available with FTS_AGAIN.
> - */
> - if (p->fts_info != FTS_D /* && p->fts_info != FTS_DNR */)
> - return (NULL);
> -
> - /* Free up any previous child list. */
> - if (sp->fts_child != NULL)
> - fts_lfree(sp->fts_child);
> -
> - if (instr == FTS_NAMEONLY) {
> - SET(FTS_NAMEONLY);
> - instr = BNAMES;
> - } else
> - instr = BCHILD;
> -
> - /*
> - * If using chdir on a relative file name and called BEFORE fts_read
> - * does its chdir to the root of a traversal, we can lose -- we need to
> - * chdir into the subdirectory, and we don't know where the current
> - * directory is, so we can't get back so that the upcoming chdir by
> - * fts_read will work.
> - */
> - if (p->fts_level != FTS_ROOTLEVEL || p->fts_accpath[0] == '/' ||
> - ISSET(FTS_NOCHDIR))
> - return (sp->fts_child = fts_build(sp, instr));
> -
> - int fd = diropen (sp, ".");
> - if (fd < 0)
> - return (sp->fts_child = NULL);
> - sp->fts_child = fts_build(sp, instr);
> - if (ISSET(FTS_CWDFD))
> - {
> - cwd_advance_fd (sp, fd, true);
> - }
> - else
> - {
> - if (fchdir(fd))
> - {
> - int saved_errno = errno;
> - close (fd);
> - __set_errno (saved_errno);
> - return NULL;
> - }
> - close (fd);
> - }
> - return (sp->fts_child);
> -}
> -
> -/* A comparison function to sort on increasing inode number.
> - For some file system types, sorting either way makes a huge
> - performance difference for a directory with very many entries,
> - but sorting on increasing values is slightly better than sorting
> - on decreasing values. The difference is in the 5% range. */
> -static int
> -fts_compare_ino (FTSENTRY const **a, FTSENTRY const **b)
> -{
> - return _GL_CMP (a[0]->fts_statp->st_ino, b[0]->fts_statp->st_ino);
> -}
> -
> -/* Map the dirent.d_type value, DTYPE, to the corresponding stat.st_mode
> - S_IF* bit and set ST.st_mode, thus clearing all other bits in that field. */
> -static void
> -set_stat_type (struct STRUCT_STAT *st, unsigned int dtype)
> -{
> - mode_t type;
> - switch (dtype)
> - {
> - case DT_BLK:
> - type = S_IFBLK;
> - break;
> - case DT_CHR:
> - type = S_IFCHR;
> - break;
> - case DT_DIR:
> - type = S_IFDIR;
> - break;
> - case DT_FIFO:
> - type = S_IFIFO;
> - break;
> - case DT_LNK:
> - type = S_IFLNK;
> - break;
> - case DT_REG:
> - type = S_IFREG;
> - break;
> - case DT_SOCK:
> - type = S_IFSOCK;
> - break;
> - default:
> - type = 0;
> - }
> - st->st_mode = type;
> -}
> -
> -#define closedir_and_clear(dirp) \
> - do \
> - { \
> - closedir (dirp); \
> - dirp = NULL; \
> - } \
> - while (0)
> -
> -#define fts_opendir(file, Pdir_fd) \
> - OPENDIRAT((! ISSET(FTS_NOCHDIR) && ISSET(FTS_CWDFD) \
> - ? sp->fts_cwd_fd : AT_FDCWD), \
> - file, \
> - (((ISSET(FTS_PHYSICAL) \
> - && ! (ISSET(FTS_COMFOLLOW) \
> - && cur->fts_level == FTS_ROOTLEVEL)) \
> - ? O_NOFOLLOW : 0)), \
> - Pdir_fd)
> -
> -/*
> - * This is the tricky part -- do not casually change *anything* in here. The
> - * idea is to build the linked list of entries that are used by fts_children
> - * and fts_read. There are lots of special cases.
> - *
> - * The real slowdown in walking the tree is the stat calls. If FTS_NOSTAT is
> - * set and it's a physical walk (so that symbolic links can't be directories),
> - * we can do things quickly. First, if it's a 4.4BSD file system, the type
> - * of the file is in the directory entry. Otherwise, we assume that the number
> - * of subdirectories in a node is equal to the number of links to the parent.
> - * The former skips all stat calls. The latter skips stat calls in any leaf
> - * directories and for any files after the subdirectories in the directory have
> - * been found, cutting the stat calls by about 2/3.
> - */
> -static FTSENTRY *
> -internal_function
> -fts_build (register FTSOBJ *sp, int type)
> -{
> - FTSENTRY *cur = sp->fts_cur;
> - bool continue_readdir = !!FTSENT_DIRP(cur);
> -
> - /* When cur->fts_dirp is non-NULL, that means we should
> - continue calling readdir on that existing DIR* pointer
> - rather than opening a new one. */
> - int dir_fd;
> - if (continue_readdir)
> - {
> - DIR *dp = FTSENT_DIRP(cur);
> - dir_fd = dirfd (dp);
> - if (dir_fd < 0)
> - {
> - int dirfd_errno = errno;
> - closedir_and_clear (FTSENT_DIRP(cur));
> - if (type == BREAD)
> - {
> - cur->fts_info = FTS_DNR;
> - cur->fts_errno = dirfd_errno;
> - }
> - return NULL;
> - }
> - }
> - else
> - {
> - /* Open the directory for reading. If this fails, we're done.
> - If being called from fts_read, set the fts_info field. */
> - if ((FTSENT_DIRP (cur) = fts_opendir(cur->fts_accpath, &dir_fd)) == NULL)
> - {
> - if (type == BREAD)
> - {
> - cur->fts_info = FTS_DNR;
> - cur->fts_errno = errno;
> - }
> - return NULL;
> - }
> - /* Rather than calling fts_stat for each and every entry encountered
> - in the readdir loop (below), stat each directory only right after
> - opening it. */
> - bool stat_optimization = cur->fts_info == FTS_NSOK;
> -
> - if (stat_optimization
> - /* Also read the stat info again after opening a directory to
> - reveal eventual changes caused by a submount triggered by
> - the traversal. But do it only for utilities which use
> - FTS_TIGHT_CYCLE_CHECK. Therefore, only find and du
> - benefit/suffer from this feature for now. */
> - || ISSET (FTS_TIGHT_CYCLE_CHECK))
> - {
> - if (!stat_optimization)
> - LEAVE_DIR (sp, cur, "4");
> - if (FSTAT (dir_fd, cur->fts_statp) != 0)
> - {
> - int fstat_errno = errno;
> - closedir_and_clear (FTSENT_DIRP(cur));
> - if (type == BREAD)
> - {
> - cur->fts_errno = fstat_errno;
> - cur->fts_info = FTS_NS;
> - }
> - __set_errno (fstat_errno);
> - return NULL;
> - }
> - if (stat_optimization)
> - cur->fts_info = FTS_D;
> - else if (! enter_dir (sp, cur))
> - {
> - int saved_errno = errno;
> - closedir_and_clear (FTSENT_DIRP(cur));
> - __set_errno (saved_errno);
> - return NULL;
> - }
> - }
> - }
> -
> - /* Maximum number of readdir entries to read at one time. This
> - limitation is to avoid reading millions of entries into memory
> - at once. When an fts_compar function is specified, we have no
> - choice: we must read all entries into memory before calling that
> - function. But when no such function is specified, we can read
> - entries in batches that are large enough to help us with inode-
> - sorting, yet not so large that we risk exhausting memory. */
> - size_t max_entries = sp->fts_compar ? SIZE_MAX : FTS_MAX_READDIR_ENTRIES;
> -
> - /*
> - * If we're going to need to stat anything or we want to descend
> - * and stay in the directory, chdir. If this fails we keep going,
> - * but set a flag so we don't chdir after the post-order visit.
> - * We won't be able to stat anything, but we can still return the
> - * names themselves. Note, that since fts_read won't be able to
> - * chdir into the directory, it will have to return different file
> - * names than before, i.e. "a/b" instead of "b". Since the node
> - * has already been visited in pre-order, have to wait until the
> - * post-order visit to return the error. There is a special case
> - * here, if there was nothing to stat then it's not an error to
> - * not be able to stat. This is all fairly nasty. If a program
> - * needed sorted entries or stat information, they had better be
> - * checking FTS_NS on the returned nodes.
> - */
> - bool descend;
> - if (continue_readdir)
> - {
> - /* When resuming a short readdir run, we already have
> - the required dirp and dir_fd. */
> - descend = true;
> - }
> - else
> - {
> - /* Try to descend unless it is a names-only fts_children,
> - or the directory is known to lack subdirectories. */
> - descend = (type != BNAMES
> - && ! (ISSET (FTS_NOSTAT) && ISSET (FTS_PHYSICAL)
> - && ! ISSET (FTS_SEEDOT)
> - && cur->fts_statp->st_nlink == MIN_DIR_NLINK
> - && (leaf_optimization (cur, dir_fd)
> - != NO_LEAF_OPTIMIZATION)));
> - if (descend || type == BREAD)
> - {
> - if (ISSET(FTS_CWDFD))
> - dir_fd = fcntl (dir_fd, F_DUPFD_CLOEXEC, STDERR_FILENO + 1);
> - if (dir_fd < 0 || fts_safe_changedir(sp, cur, dir_fd, NULL)) {
> - if (descend && type == BREAD)
> - cur->fts_errno = errno;
> - cur->fts_flags |= FTS_DONTCHDIR;
> - descend = false;
> - closedir_and_clear(FTSENT_DIRP(cur));
> - if (ISSET(FTS_CWDFD) && 0 <= dir_fd)
> - close (dir_fd);
> - FTSENT_DIRP(cur) = NULL;
> - } else
> - descend = true;
> - }
> - }
> -
> - /*
> - * Figure out the max file name length that can be stored in the
> - * current buffer -- the inner loop allocates more space as necessary.
> - * We really wouldn't have to do the maxlen calculations here, we
> - * could do them in fts_read before returning the name, but it's a
> - * lot easier here since the length is part of the dirent structure.
> - *
> - * If not changing directories set a pointer so that can just append
> - * each new component into the file name.
> - */
> - size_t len = NAPPEND(cur);
> - char *cp;
> - if (ISSET(FTS_NOCHDIR)) {
> - cp = sp->fts_path + len;
> - *cp++ = '/';
> - } else {
> - /* GCC, you're too verbose. */
> - cp = NULL;
> - }
> - len++;
> - size_t maxlen = sp->fts_pathlen - len;
> -
> - ptrdiff_t level = cur->fts_level + 1;
> -
> - /* Read the directory, attaching each entry to the "link" pointer. */
> - bool doadjust = false;
> - register FTSENTRY *head = NULL;
> - FTSENTRY *tail = NULL;
> - register size_t nitems = 0;
> - bool sort_by_inode = false;
> - while (FTSENT_DIRP(cur)) {
> - __set_errno (0);
> - struct dirent *dp = readdir(FTSENT_DIRP(cur));
> - if (dp == NULL) {
> - /* Some readdir()s do not absorb ENOENT (dir
> - deleted but open). This bug was fixed in
> - glibc 2.3 (2002). */
> -#if ! (2 < __GLIBC__ + (3 <= __GLIBC_MINOR__))
> - if (errno == ENOENT)
> - errno = 0;
> -#endif
> - if (errno) {
> - cur->fts_errno = errno;
> - /* If we've not read any items yet, treat
> - the error as if we can't access the dir. */
> - cur->fts_info = (continue_readdir || nitems)
> - ? FTS_ERR : FTS_DNR;
> - }
> - closedir_and_clear(FTSENT_DIRP(cur));
> - break;
> - }
> - if (!ISSET(FTS_SEEDOT) && ISDOT(dp->d_name))
> - continue;
> -
> - size_t d_namelen = _D_EXACT_NAMLEN (dp);
> - register FTSENTRY *p = fts_alloc (sp, dp->d_name, d_namelen);
> - if (!p)
> - goto mem1;
> - if (d_namelen >= maxlen) {
> - /* include space for NUL */
> - uintptr_t oldaddr = (uintptr_t) sp->fts_path;
> - if (! fts_palloc(sp, d_namelen + len + 1)) {
> - /*
> - * No more memory. Save
> - * errno, free up the current structure and the
> - * structures already allocated.
> - */
> -mem1: ;
> - int saved_errno = errno;
> - free(FTSENT_WRAPPER(p));
> - fts_lfree(head);
> - closedir_and_clear(FTSENT_DIRP(cur));
> - cur->fts_info = FTS_ERR;
> - SET(FTS_STOP);
> - __set_errno (saved_errno);
> - return (NULL);
> - }
> - /* Did realloc() change the pointer? */
> - if (oldaddr != (uintptr_t) sp->fts_path) {
> - doadjust = true;
> - if (ISSET(FTS_NOCHDIR))
> - cp = sp->fts_path + len;
> - }
> - maxlen = sp->fts_pathlen - len;
> - }
> -
> - size_t new_len = len + d_namelen;
> - if (new_len < len) {
> - /*
> - * In the unlikely event that we would end up
> - * with a file name longer than SIZE_MAX, free up
> - * the current structure and the structures already
> - * allocated, then error out with ENAMETOOLONG.
> - */
> - free(FTSENT_WRAPPER(p));
> - fts_lfree(head);
> - closedir_and_clear(FTSENT_DIRP(cur));
> - cur->fts_info = FTS_ERR;
> - SET(FTS_STOP);
> - __set_errno (ENAMETOOLONG);
> - return (NULL);
> - }
> - p->fts_level = level;
> - p->fts_parent = sp->fts_cur;
> - p->fts_pathlen = new_len;
> -
> - /* Store dirent.d_ino, in case we need to sort
> - entries before processing them. */
> - p->fts_statp->st_ino = D_INO (dp);
> -
> - /* Build a file name for fts_stat to stat. */
> - if (ISSET(FTS_NOCHDIR)) {
> - p->fts_accpath = p->fts_path;
> - memmove(cp, p->fts_name, p->fts_namelen + 1);
> - } else
> - p->fts_accpath = p->fts_name;
> -
> - if (sp->fts_compar == NULL || ISSET(FTS_DEFER_STAT)) {
> - /* Record what fts_read will have to do with this
> - entry. In many cases, it will simply fts_stat it,
> - but we can take advantage of any d_type information
> - to optimize away the unnecessary stat calls. I.e.,
> - if FTS_NOSTAT is in effect, we don't need device
> - numbers unconditionally (FTS_MOUNT) and we're not
> - following symlinks (FTS_PHYSICAL) and d_type
> - indicates this is *not* a directory, then we won't
> - have to stat it at all. If it *is* a directory,
> - then (currently) we stat it regardless, in order to
> - get device and inode numbers. Some day we might
> - optimize that away, too, for directories where
> - d_ino is known to be valid. */
> - bool skip_stat = (ISSET(FTS_NOSTAT)
> - && DT_IS_KNOWN(dp)
> - && ! DT_MUST_BE(dp, DT_DIR)
> - && (ISSET(FTS_PHYSICAL)
> - || ! DT_MUST_BE(dp, DT_LNK))
> - && ! ISSET(FTS_MOUNT));
> - p->fts_info = FTS_NSOK;
> - /* Propagate dirent.d_type information back
> - to caller, when possible. */
> - set_stat_type (p->fts_statp, D_TYPE (dp));
> - fts_set_stat_required(p, !skip_stat);
> - } else {
> - p->fts_info = fts_stat(sp, p, false);
> - }
> -
> - /* We walk in directory order so "ls -f" doesn't get upset. */
> - p->fts_link = NULL;
> - if (head == NULL)
> - head = tail = p;
> - else {
> - tail->fts_link = p;
> - tail = p;
> - }
> -
> - /* If there are many entries, no sorting function has been
> - specified, and this file system is of a type that may be
> - slow with a large number of entries, arrange to sort the
> - directory entries on increasing inode numbers.
> -
> - The NITEMS comparison uses ==, not >, because the test
> - needs to be tried at most once once, and NITEMS will exceed
> - the threshold after it is incremented below. */
> - if (nitems == _FTS_INODE_SORT_DIR_ENTRIES_THRESHOLD
> - && !sp->fts_compar)
> - sort_by_inode = dirent_inode_sort_may_be_useful (cur, dir_fd);
> -
> - ++nitems;
> - if (max_entries <= nitems) {
> - /* When there are too many dir entries, leave
> - fts_dirp open, so that a subsequent fts_read
> - can take up where we leave off. */
> - break;
> - }
> - }
> -
> - /*
> - * If realloc() changed the address of the file name, adjust the
> - * addresses for the rest of the tree and the dir list.
> - */
> - if (doadjust)
> - fts_padjust(sp, head);
> -
> - /*
> - * If not changing directories, reset the file name back to original
> - * state.
> - */
> - if (ISSET(FTS_NOCHDIR)) {
> - if (len == sp->fts_pathlen || nitems == 0)
> - --cp;
> - *cp = '\0';
> - }
> -
> - /*
> - * If descended after called from fts_children or after called from
> - * fts_read and nothing found, get back. At the root level we use
> - * the saved fd; if one of fts_open()'s arguments is a relative name
> - * to an empty directory, we wind up here with no other way back. If
> - * can't get back, we're done.
> - */
> - if (!continue_readdir && descend && (type == BCHILD || !nitems) &&
> - (cur->fts_level == FTS_ROOTLEVEL
> - ? restore_initial_cwd(sp)
> - : fts_safe_changedir(sp, cur->fts_parent, -1, ".."))) {
> - cur->fts_info = FTS_ERR;
> - SET(FTS_STOP);
> - fts_lfree(head);
> - return (NULL);
> - }
> -
> - /* If didn't find anything, return NULL. */
> - if (!nitems) {
> - if (type == BREAD
> - && cur->fts_info != FTS_DNR && cur->fts_info != FTS_ERR)
> - cur->fts_info = FTS_DP;
> - fts_lfree(head);
> - return (NULL);
> - }
> -
> - if (sort_by_inode) {
> - sp->fts_compar = FTS_COMPAR_CAST (fts_compare_ino);
> - head = fts_sort (sp, head, nitems);
> - sp->fts_compar = NULL;
> - }
> -
> - /* Sort the entries. */
> - if (sp->fts_compar && nitems > 1)
> - head = fts_sort(sp, head, nitems);
> - return (head);
> -}
> -
> -#if GNULIB_FTS_DEBUG
> -
> -struct devino {
> - intmax_t dev, ino;
> -};
> -#define PRINT_DEVINO "(%jd,%jd)"
> -
> -static struct devino
> -getdevino (int fd)
> -{
> - struct STRUCT_STAT st;
> - return (fd == AT_FDCWD
> - ? (struct devino) { -1, 0 }
> - : FSTAT (fd, &st) == 0
> - ? (struct devino) { st.st_dev, st.st_ino }
> - : (struct devino) { -1, errno });
> -}
> -
> -/* Walk ->fts_parent links starting at E_CURR, until the root of the
> - current hierarchy. There should be a directory with dev/inode
> - matching those of AD. If not, print a lot of diagnostics. */
> -static void
> -find_matching_ancestor (FTSENTRY const *e_curr, struct Active_dir const *ad)
> -{
> - for (FTSENTRY const *ent = e_curr;
> - ent->fts_level >= FTS_ROOTLEVEL;
> - ent = ent->fts_parent)
> - {
> - if (ad->ino == ent->fts_statp->st_ino
> - && ad->dev == ent->fts_statp->st_dev)
> - return;
> - }
> - printf ("ERROR: tree dir, %s, not active\n", ad->fts_ent->fts_accpath);
> - printf ("active dirs:\n");
> - for (FTSENTRY const *ent = e_curr;
> - ent->fts_level >= FTS_ROOTLEVEL;
> - ent = ent->fts_parent)
> - printf (" %s(%"PRIuMAX"/%"PRIuMAX") to %s(%"PRIuMAX"/%"PRIuMAX")...\n",
> - ad->fts_ent->fts_accpath,
> - (uintmax_t) ad->dev,
> - (uintmax_t) ad->ino,
> - ent->fts_accpath,
> - (uintmax_t) ent->fts_statp->st_dev,
> - (uintmax_t) ent->fts_statp->st_ino);
> -}
> -
> -void
> -fts_cross_check (FTSOBJ const *sp)
> -{
> - if ( ! ISSET (FTS_TIGHT_CYCLE_CHECK))
> - return;
> -
> - FTSENTRY const *ent = sp->fts_cur;
> -
> - Dprintf (("fts-cross-check cur=%s\n", ent->fts_path));
> - /* Make sure every parent dir is in the tree. */
> - for (FTSENTRY const *t = ent->fts_parent;
> - t->fts_level >= FTS_ROOTLEVEL;
> - t = t->fts_parent)
> - {
> - struct Active_dir ad;
> - ad.ino = t->fts_statp->st_ino;
> - ad.dev = t->fts_statp->st_dev;
> - if ( ! hash_lookup (sp->fts_cycle.ht, &ad))
> - printf ("ERROR: active dir, %s, not in tree\n", t->fts_path);
> - }
> -
> - /* Make sure every dir in the tree is an active dir.
> - But ENT is not necessarily a directory. If so, just skip this part. */
> - if (ent->fts_parent->fts_level >= FTS_ROOTLEVEL
> - && (ent->fts_info == FTS_DP
> - || ent->fts_info == FTS_D))
> - for (struct Active_dir *ad = hash_get_first (sp->fts_cycle.ht);
> - ad != NULL;
> - ad = hash_get_next (sp->fts_cycle.ht, ad))
> - {
> - find_matching_ancestor (ent, ad);
> - }
> -}
> -
> -static bool
> -same_fd (int fd1, int fd2)
> -{
> - struct STRUCT_STAT sb1, sb2;
> - return (FSTAT (fd1, &sb1) == 0
> - && FSTAT (fd2, &sb2) == 0
> - && psame_inode (&sb1, &sb2));
> -}
> -
> -static void
> -fd_ring_print (FTSOBJ const *sp, FILE *stream, char const *msg)
> -{
> - if (!fts_debug)
> - return;
> - I_ring const *fd_ring = &sp->fts_fd_ring;
> - struct devino cwd = getdevino (sp->fts_cwd_fd);
> - fprintf (stream, "=== %s ========== "PRINT_DEVINO"\n", msg, cwd.dev, cwd.ino);
> - if (i_ring_empty (fd_ring))
> - return;
> -
> - unsigned int i = fd_ring->ir_front;
> - while (true)
> - {
> - int fd = fd_ring->ir_data[i];
> - if (fd < 0)
> - fprintf (stream, "%u: %d:\n", i, fd);
> - else
> - {
> - struct devino wd = getdevino (fd);
> - fprintf (stream, "%u: %d: "PRINT_DEVINO"\n", i, fd, wd.dev, wd.ino);
> - }
> - if (i == fd_ring->ir_back)
> - break;
> - i = (i + I_RING_SIZE - 1) % I_RING_SIZE;
> - }
> -}
> -
> -/* Ensure that each file descriptor on the fd_ring matches a
> - parent, grandparent, etc. of the current working directory. */
> -static void
> -fd_ring_check (FTSOBJ const *sp)
> -{
> - if (!fts_debug)
> - return;
> -
> - /* Make a writable copy. */
> - I_ring fd_w = sp->fts_fd_ring;
> -
> - int cwd_fd = sp->fts_cwd_fd;
> - cwd_fd = fcntl (cwd_fd, F_DUPFD_CLOEXEC, STDERR_FILENO + 1);
> - struct devino dot = getdevino (cwd_fd);
> - fprintf (stderr, "===== check ===== cwd: "PRINT_DEVINO"\n",
> - dot.dev, dot.ino);
> - while ( ! i_ring_empty (&fd_w))
> - {
> - int fd = i_ring_pop (&fd_w);
> - if (0 <= fd)
> - {
> - int open_flags = O_SEARCH | O_CLOEXEC;
> - int parent_fd = openat (cwd_fd, "..", open_flags);
> - if (parent_fd < 0)
> - {
> - // Warn?
> - break;
> - }
> - if (!same_fd (fd, parent_fd))
> - {
> - struct devino cwd = getdevino (fd);
> - fprintf (stderr, "ring : "PRINT_DEVINO"\n", cwd.dev, cwd.ino);
> - struct devino c2 = getdevino (parent_fd);
> - fprintf (stderr, "parent: "PRINT_DEVINO"\n", c2.dev, c2.ino);
> - fts_assert (0);
> - }
> - close (cwd_fd);
> - cwd_fd = parent_fd;
> - }
> - }
> - close (cwd_fd);
> -}
> -#endif
> -
> -static unsigned short int
> -internal_function
> -fts_stat(FTSOBJ *sp, register FTSENTRY *p, bool follow)
> -{
> - if (ISSET (FTS_LOGICAL)
> - || (ISSET (FTS_COMFOLLOW) && p->fts_level == FTS_ROOTLEVEL))
> - follow = true;
> -
> - struct STRUCT_STAT *sbp = p->fts_statp;
> -
> - /*
> - * If doing a logical walk, or application requested FTS_FOLLOW, do
> - * a stat(2). If that fails, check for a nonexistent symlink. If
> - * fail, set the errno from the stat call.
> - */
> - int flags = follow ? 0 : AT_SYMLINK_NOFOLLOW;
> - if (FSTATAT (sp->fts_cwd_fd, p->fts_accpath, sbp, flags) < 0)
> - {
> - if (follow && errno == ENOENT
> - && 0 <= FSTATAT (sp->fts_cwd_fd, p->fts_accpath, sbp,
> - AT_SYMLINK_NOFOLLOW))
> - {
> - __set_errno (0);
> - return FTS_SLNONE;
> - }
> -
> - p->fts_errno = errno;
> - memset (sbp, 0, sizeof *sbp);
> - return FTS_NS;
> - }
> -
> - if (S_ISDIR(sbp->st_mode)) {
> - if (ISDOT(p->fts_name)) {
> - /* Command-line "." and ".." are real directories. */
> - return (p->fts_level == FTS_ROOTLEVEL ? FTS_D : FTS_DOT);
> - }
> -
> - return (FTS_D);
> - }
> - if (S_ISLNK(sbp->st_mode))
> - return (FTS_SL);
> - if (S_ISREG(sbp->st_mode))
> - return (FTS_F);
> - return (FTS_DEFAULT);
> -}
> -
> -static int
> -fts_compar (void const *a, void const *b)
> -{
> - /* Convert A and B to the correct types, to pacify the compiler, and
> - for portability to bizarre hosts where "void const *" and "FTSENT
> - const **" differ in runtime representation. The comparison
> - function cannot modify *a and *b, but there is no compile-time
> - check for this. */
> - FTSENTRY const **pa = (FTSENTRY const **) a;
> - FTSENTRY const **pb = (FTSENTRY const **) b;
> - return FTSENT_FTS(pa[0])->fts_compar (pa, pb);
> -}
> -
> -static FTSENTRY *
> -internal_function
> -fts_sort (FTSOBJ *sp, FTSENTRY *head, register size_t nitems)
> -{
> - register FTSENTRY **ap, *p;
> -
> - /* On most modern hosts, void * and FTSENT ** have the same
> - run-time representation, and one can convert sp->fts_compar to
> - the type qsort expects without problem. Use the heuristic that
> - this is OK if the two pointer types are the same size, and if
> - converting FTSENT ** to uintptr_t is the same as converting
> - FTSENT ** to void * and then to uintptr_t. This heuristic isn't
> - valid in general but we don't know of any counterexamples. */
> - FTSENTRY *dummy;
> - int (*compare) (void const *, void const *) =
> - ((sizeof &dummy == sizeof (void *)
> - && (uintptr_t) &dummy == (uintptr_t) (void *) &dummy)
> - ? (int (*) (void const *, void const *)) sp->fts_compar
> - : fts_compar);
> -
> - /*
> - * Construct an array of pointers to the structures and call qsort(3).
> - * Reassemble the array in the order returned by qsort. If unable to
> - * sort for memory reasons, return the directory entries in their
> - * current order. Allocate enough space for the current needs plus
> - * 40 so don't realloc one entry at a time.
> - */
> - if (nitems > sp->fts_nitems) {
> - sp->fts_nitems = nitems + 40;
> - FTSENTRY **a;
> - if (! (a = reallocarray (sp->fts_array,
> - sp->fts_nitems, sizeof *a))) {
> - free(sp->fts_array);
> - sp->fts_array = NULL;
> - sp->fts_nitems = 0;
> - return (head);
> - }
> - sp->fts_array = a;
> - }
> - for (ap = sp->fts_array, p = head; p; p = p->fts_link)
> - *ap++ = p;
> - qsort((void *)sp->fts_array, nitems, sizeof(FTSENTRY *), compare);
> - for (head = *(ap = sp->fts_array); --nitems; ++ap)
> - ap[0]->fts_link = ap[1];
> - ap[0]->fts_link = NULL;
> - return (head);
> -}
> -
> -static FTSENTRY *
> -internal_function
> -fts_alloc (FTSOBJ *sp, const char *name, register size_t namelen)
> -{
> - /*
> - * The file name is a variable length array. Allocate the FTSENT
> - * structure and the file name in one chunk.
> - */
> - size_t len = FLEXSIZEOF(FTSENT, fts_name, namelen + 1);
> - register FTSENTRY *p;
> -#if !_LIBC
> - p = malloc(len);
> - if (p == NULL)
> - return (NULL);
> -#else
> - /*
> - * For glibc, we use a wrapper struct to provide the extra required
> - * fields without changing the FSENT layout.
> - */
> - len += sizeof (struct FTSENT_wrapper);
> - struct FTSENT_wrapper *wrapper = malloc(len);
> - if (wrapper == NULL)
> - return (NULL);
> - p = &wrapper->ent;
> - p->fts_statp = &wrapper->fts_stat;
> -#endif
> -
> - /* Copy the name and guarantee NUL termination. */
> - memcpy(p->fts_name, name, namelen);
> - p->fts_name[namelen] = '\0';
> -
> - p->fts_namelen = namelen;
> - FTSENT_FTS(p)= sp;
> - p->fts_path = sp->fts_path;
> - p->fts_errno = 0;
> - FTSENT_DIRP(p) = NULL;
> - p->fts_flags = 0;
> - p->fts_instr = FTS_NOINSTR;
> - p->fts_number = 0;
> - p->fts_pointer = NULL;
> - return (p);
> -}
> -
> -static void
> -internal_function
> -fts_lfree (register FTSENTRY *head)
> -{
> - int saved_errno = errno;
> -
> - /* Free a linked list of structures. */
> - register FTSENTRY *p;
> - while ((p = head)) {
> - head = head->fts_link;
> - if (FTSENT_DIRP(p))
> - closedir (FTSENT_DIRP(p));
> - free(FTSENT_WRAPPER(p));
> - }
> -
> - __set_errno (saved_errno);
> -}
> -
> -/*
> - * Allow essentially unlimited file name lengths; find, rm, ls should
> - * all work on any tree. Most systems will allow creation of file
> - * names much longer than MAXPATHLEN, even though the kernel won't
> - * resolve them. Add the size (not just what's needed) plus 256 bytes
> - * so don't realloc the file name 2 bytes at a time.
> - */
> -static bool
> -internal_function
> -fts_palloc (FTSOBJ *sp, size_t more)
> -{
> - size_t new_len = sp->fts_pathlen + more + 256;
> -
> - /*
> - * See if fts_pathlen would overflow.
> - */
> - if (new_len < sp->fts_pathlen) {
> - free(sp->fts_path);
> - sp->fts_path = NULL;
> - __set_errno (ENAMETOOLONG);
> - return false;
> - }
> - sp->fts_pathlen = new_len;
> - char *p = realloc(sp->fts_path, sp->fts_pathlen);
> - if (p == NULL) {
> - free(sp->fts_path);
> - sp->fts_path = NULL;
> - return false;
> - }
> - sp->fts_path = p;
> - return true;
> -}
> -
> -/*
> - * When the file name is realloc'd, have to fix all of the pointers in
> - * structures already returned.
> - */
> -static void
> -internal_function
> -fts_padjust (FTSOBJ *sp, FTSENTRY *head)
> -{
> - char *addr = sp->fts_path;
> -
> - /* This code looks at bit-patterns of freed pointers to
> - relocate them, so it relies on undefined behavior. If this
> - trick does not work on your platform, please report a bug. */
> -
> -#define ADJUST(p) do { \
> - uintptr_t old_accpath = (uintptr_t) (p)->fts_accpath; \
> - if (old_accpath != (uintptr_t) (p)->fts_name) { \
> - (p)->fts_accpath = \
> - addr + (old_accpath - (uintptr_t) (p)->fts_path); \
> - } \
> - (p)->fts_path = addr; \
> -} while (0)
> - /* Adjust the current set of children. */
> - for (FTSENTRY *p = sp->fts_child; p; p = p->fts_link)
> - ADJUST(p);
> -
> - /* Adjust the rest of the tree, including the current level. */
> - for (FTSENTRY *p = head; p->fts_level >= FTS_ROOTLEVEL;) {
> - ADJUST(p);
> - p = p->fts_link ? p->fts_link : p->fts_parent;
> - }
> -}
> -
> -static size_t
> -internal_function _GL_ATTRIBUTE_PURE
> -fts_maxarglen (char * const *argv)
> -{
> - size_t max;
> -
> - for (max = 0; *argv; ++argv) {
> - size_t len = strlen(*argv);
> - if (len > max)
> - max = len;
> - }
> - return (max + 1);
> -}
> -
> -/*
> - * Change to dir specified by fd or file name without getting
> - * tricked by someone changing the world out from underneath us.
> - * Assumes p->fts_statp->st_dev and p->fts_statp->st_ino are filled in.
> - * If FD is non-negative, expect it to be used after this function returns,
> - * and to be closed eventually. So don't pass e.g., 'dirfd(dirp)' and then
> - * do closedir(dirp), because that would invalidate the saved FD.
> - * Upon failure, close FD immediately and return nonzero.
> - */
> -static int
> -internal_function
> -fts_safe_changedir (FTSOBJ *sp, FTSENTRY *p, int fd, char const *dir)
> -{
> - fts_assert (0 <= fd || dir != NULL);
> - bool is_dotdot = dir && streq (dir, "..");
> -
> - /* This clause handles the unusual case in which FTS_NOCHDIR
> - is specified, along with FTS_CWDFD. In that case, there is
> - no need to change even the virtual cwd file descriptor.
> - However, if FD is non-negative, we do close it here. */
> - if (ISSET (FTS_NOCHDIR))
> - {
> - if (ISSET (FTS_CWDFD) && 0 <= fd)
> - close (fd);
> - return 0;
> - }
> -
> - if (fd < 0 && is_dotdot && ISSET (FTS_CWDFD))
> - {
> - /* When possible, skip the diropen and subsequent fstat+dev/ino
> - comparison. I.e., when changing to parent directory
> - (chdir ("..")), use a file descriptor from the ring and
> - save the overhead of diropen+fstat, as well as avoiding
> - failure when we lack "x" access to the virtual cwd. */
> - if ( ! i_ring_empty (&sp->fts_fd_ring))
> - {
> - int parent_fd;
> - fd_ring_print (sp, stderr, "pre-pop");
> - parent_fd = i_ring_pop (&sp->fts_fd_ring);
> - if (0 <= parent_fd)
> - {
> - fd = parent_fd;
> - dir = NULL;
> - }
> - }
> - }
> -
> - int newfd = fd;
> - if (fd < 0 && (newfd = diropen (sp, dir)) < 0)
> - return -1;
> -
> - /* The following dev/inode check is necessary if we're doing a
> - "logical" traversal (through symlinks, a la chown -L), if the
> - system lacks O_NOFOLLOW support, or if we're changing to ".."
> - (but not via a popped file descriptor). When changing to the
> - name "..", O_NOFOLLOW can't help. In general, when the target is
> - not "..", diropen's use of O_NOFOLLOW ensures we don't mistakenly
> - follow a symlink, so we can avoid the expense of this fstat. */
> - int ret;
> - if (ISSET(FTS_LOGICAL) || ! HAVE_WORKING_O_NOFOLLOW
> - || (dir && streq (dir, "..")))
> - {
> - struct STRUCT_STAT sb;
> - if (FSTAT (newfd, &sb))
> - {
> - ret = -1;
> - goto bail;
> - }
> - if (p->fts_statp->st_dev != sb.st_dev
> - || p->fts_statp->st_ino != sb.st_ino)
> - {
> - __set_errno (ENOENT); /* disinformation */
> - ret = -1;
> - goto bail;
> - }
> - }
> -
> - if (ISSET(FTS_CWDFD))
> - {
> - cwd_advance_fd (sp, newfd, ! is_dotdot);
> - return 0;
> - }
> -
> - ret = fchdir(newfd);
> -bail:
> - if (fd < 0)
> - {
> - int oerrno = errno;
> - (void)close(newfd);
> - __set_errno (oerrno);
> - }
> - return ret;
> -}
> diff --git a/io/fts64-time64.c b/io/fts64-time64.c
> index 6a1053194e..29f2dbc28c 100644
> --- a/io/fts64-time64.c
> +++ b/io/fts64-time64.c
> @@ -33,5 +33,5 @@
> # define STRUCT_STATFS statfs64
> # define FSTATFS __fstatfs64
>
> -# include "fts.c"
> +# include "fts-common.c"
> #endif
> diff --git a/io/fts64.c b/io/fts64.c
> index a6f607a873..1efa06ab3b 100644
> --- a/io/fts64.c
> +++ b/io/fts64.c
> @@ -16,11 +16,11 @@
> License along with the GNU C Library; if not, see
> <https://www.gnu.org/licenses/>. */
>
> -#define FTS_OPEN fts64_open
> -#define FTS_CLOSE fts64_close
> -#define FTS_READ fts64_read
> -#define FTS_SET fts64_set
> -#define FTS_CHILDREN fts64_children
> +#define FTS_OPEN __fts64_open
> +#define FTS_CLOSE __fts64_close
> +#define FTS_READ __fts64_read
> +#define FTS_SET __fts64_set
> +#define FTS_CHILDREN __fts64_children
> #define FTSOBJ FTS64
> #define FTSENTRY FTSENT64
> #define INO_T ino64_t
> @@ -30,4 +30,30 @@
> #define STRUCT_STATFS statfs64
> #define FSTATFS __fstatfs64
>
> -#include "fts.c"
> +#define fts_open __rename_fts_open
> +#define fts_close __rename_fts_close
> +#define fts_read __rename_fts_read
> +#define fts_set __rename_fts_set
> +#define fts_children __rename_fts_children
> +
> +#include "fts-common.c"
> +
> +#undef fts_open
> +#undef fts_close
> +#undef fts_read
> +#undef fts_set
> +#undef fts_children
> +
> +weak_alias (__fts64_open, fts64_open)
> +weak_alias (__fts64_close, fts64_close)
> +weak_alias (__fts64_read, fts64_read)
> +weak_alias (__fts64_set, fts64_set)
> +weak_alias (__fts64_children, fts64_children)
> +
> +#ifdef __OFF_T_MATCHES_OFF64_T
> +weak_alias (__fts64_open, fts_open)
> +weak_alias (__fts64_close, fts_close)
> +weak_alias (__fts64_read, fts_read)
> +weak_alias (__fts64_set, fts_set)
> +weak_alias (__fts64_children, fts_children)
> +#endif
> diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n64/fts.c b/sysdeps/unix/sysv/linux/mips/mips64/n64/fts.c
> deleted file mode 100644
> index d0c62e6195..0000000000
> --- a/sysdeps/unix/sysv/linux/mips/mips64/n64/fts.c
> +++ /dev/null
> @@ -1 +0,0 @@
> -#include <io/fts.c>
> diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n64/fts64.c b/sysdeps/unix/sysv/linux/mips/mips64/n64/fts64.c
> deleted file mode 100644
> index 2472f8bf75..0000000000
> --- a/sysdeps/unix/sysv/linux/mips/mips64/n64/fts64.c
> +++ /dev/null
> @@ -1 +0,0 @@
> -#include <io/fts64.c>
> diff --git a/sysdeps/unix/sysv/linux/x86_64/x32/fts.c b/sysdeps/unix/sysv/linux/x86_64/x32/fts.c
> deleted file mode 100644
> index 980573ed68..0000000000
> --- a/sysdeps/unix/sysv/linux/x86_64/x32/fts.c
> +++ /dev/null
> @@ -1 +0,0 @@
> -#include <sysdeps/wordsize-64/fts.c>
> diff --git a/sysdeps/unix/sysv/linux/x86_64/x32/fts64.c b/sysdeps/unix/sysv/linux/x86_64/x32/fts64.c
> deleted file mode 100644
> index 221d1b5608..0000000000
> --- a/sysdeps/unix/sysv/linux/x86_64/x32/fts64.c
> +++ /dev/null
> @@ -1 +0,0 @@
> -#include <sysdeps/wordsize-64/fts64.c>
> diff --git a/sysdeps/wordsize-64/fts.c b/sysdeps/wordsize-64/fts.c
> deleted file mode 100644
> index 159dc1febe..0000000000
> --- a/sysdeps/wordsize-64/fts.c
> +++ /dev/null
> @@ -1,19 +0,0 @@
> -#define fts64_open __rename_fts64_open
> -#define fts64_close __rename_fts64_close
> -#define fts64_read __rename_fts64_read
> -#define fts64_set __rename_fts64_set
> -#define fts64_children __rename_fts64_children
> -
> -#include "../../io/fts.c"
> -
> -#undef fts64_open
> -#undef fts64_close
> -#undef fts64_read
> -#undef fts64_set
> -#undef fts64_children
> -
> -weak_alias (fts_open, fts64_open)
> -weak_alias (fts_close, fts64_close)
> -weak_alias (fts_read, fts64_read)
> -weak_alias (fts_set, fts64_set)
> -weak_alias (fts_children, fts64_children)
> diff --git a/sysdeps/wordsize-64/fts64.c b/sysdeps/wordsize-64/fts64.c
> deleted file mode 100644
> index f2848fc3e4..0000000000
> --- a/sysdeps/wordsize-64/fts64.c
> +++ /dev/null
> @@ -1 +0,0 @@
> -/* Defined in fts.c. */
More information about the Libc-alpha
mailing list