diff --git a/SHARED-FILES b/SHARED-FILES index b9d9cdc9d0..126aaf096d 100644 --- a/SHARED-FILES +++ b/SHARED-FILES @@ -36,8 +36,22 @@ gnulib: # Merged from gnulib 2025-10-29 (gnulib commit 1790ef25d8) include/intprops.h include/intprops-internal.h + # Merged from gnulib 2026-02-16 + include/assure.h + include/flexmember.h + include/hash.h + include/next-prime.h + include/xalloc-oversized.h # Merged from gnulib 2021-09-21 include/regex.h + # Merged from gnulib 2026-02-16 + io/cycle-check.c + io/cycle-check.h + io/dev-ino.h + io/fts-cycle.c + io/fts.c + io/i-ring.c + io/same-inode.h locale/programs/3level.h # Merged from gnulib 2014-6-23 malloc/obstack.c @@ -47,7 +61,9 @@ gnulib: misc/error.c misc/error.h misc/getpass.c + misc/hash.c misc/mkdtemp.c + misc/next-prime.c # Merged from gnulib 2021-09-21 misc/sys/cdefs.h posix/fnmatch_loop.c diff --git a/dirent/scandirat.c b/dirent/scandirat.c index 31ec9662b0..d40c0828f1 100644 --- a/dirent/scandirat.c +++ b/dirent/scandirat.c @@ -23,7 +23,8 @@ __scandirat (int dfd, const char *dir, struct dirent ***namelist, int (*select) (const struct dirent *), int (*cmp) (const struct dirent **, const struct dirent **)) { - return __scandir_tail (__opendirat (dfd, dir), namelist, select, cmp); + return __scandir_tail (__opendirat (dfd, dir, 0, NULL), namelist, select, + cmp); } weak_alias (__scandirat, scandirat) #endif diff --git a/dirent/scandirat64.c b/dirent/scandirat64.c index fb010fac28..cb1821174e 100644 --- a/dirent/scandirat64.c +++ b/dirent/scandirat64.c @@ -24,7 +24,8 @@ scandirat64 (int dfd, const char *dir, struct dirent64 ***namelist, int (*select) (const struct dirent64 *), int (*cmp) (const struct dirent64 **, const struct dirent64 **)) { - return __scandir64_tail (__opendirat (dfd, dir), namelist, select, cmp); + return __scandir64_tail (__opendirat (dfd, dir, 0, NULL), namelist, select, + cmp); } #if _DIRENT_MATCHES_DIRENT64 diff --git a/include/assure.h b/include/assure.h new file mode 100644 index 0000000000..edc72591d1 --- /dev/null +++ b/include/assure.h @@ -0,0 +1,57 @@ +/* Run-time assert-like macros. + + Copyright (C) 2014-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 . */ + +/* Written by Paul Eggert. */ + +#ifndef _GL_ASSURE_H +#define _GL_ASSURE_H + +#include +#include "verify.h" + +/* Evaluate an assertion E that is guaranteed to be true. + If NDEBUG is not defined, abort the program if E is false. + If NDEBUG is defined, the compiler can assume E and behavior is + undefined if E is false, fails to evaluate, or has side effects. + + Unlike standard 'assert', this macro evaluates E even when NDEBUG + is defined, so as to catch typos, avoid some GCC warnings, and + improve performance when E is simple enough. + + Also see the documentation for 'assume' in verify.h. */ + +#ifdef NDEBUG +# define affirm(E) assume (E) +#else +# define affirm(E) assert (E) +#endif + +/* Check E's value at runtime, and report an error and abort if not. + However, do nothing if NDEBUG is defined. + + Unlike standard 'assert', this macro compiles E even when NDEBUG + is defined, so as to catch typos and avoid some GCC warnings. + Unlike 'affirm', it is OK for E to use hard-to-optimize features, + since E is not executed if NDEBUG is defined. */ + +#ifdef NDEBUG +# define assure(E) ((void) (0 && (E))) +#else +# define assure(E) assert (E) +#endif + +#endif diff --git a/include/dirent.h b/include/dirent.h index d7567f5e86..9a4bd5a1e3 100644 --- a/include/dirent.h +++ b/include/dirent.h @@ -16,7 +16,8 @@ struct scandir_cancel_struct /* Now define the internal interfaces. */ extern DIR *__opendir (const char *__name) attribute_hidden; -extern DIR *__opendirat (int dfd, const char *__name) attribute_hidden; +extern DIR *__opendirat (int dfd, const char *__nam, int extra_flags, + int *pnew_fd) attribute_hidden; extern DIR *__fdopendir (int __fd) attribute_hidden; extern int __closedir (DIR *__dirp) attribute_hidden; extern struct dirent *__readdir (DIR *__dirp) attribute_hidden; diff --git a/include/flexmember.h b/include/flexmember.h new file mode 100644 index 0000000000..c6bb2842ec --- /dev/null +++ b/include/flexmember.h @@ -0,0 +1,77 @@ +/* Sizes of structs with flexible array members. + + Copyright 2016-2026 Free Software Foundation, Inc. + + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . + + Written by Paul Eggert. */ + +/* This file uses _Alignof. */ +#if !_LIBC && !_GL_CONFIG_H_INCLUDED + #error "Please include config.h first." +#endif + +#include + +/* Nonzero multiple of alignment of TYPE, suitable for FLEXSIZEOF below. + If _Alignof might not exist or might not work correctly on + structs with flexible array members, use a pessimistic bound that is + safe in practice even if FLEXIBLE_ARRAY_MEMBER is 1. + Otherwise, use _Alignof to get a tighter bound. */ + +#if !defined __STDC_VERSION__ || __STDC_VERSION__ < 201112 || defined _Alignof +# define FLEXALIGNOF(type) (sizeof (type) & ~ (sizeof (type) - 1)) +#else +# define FLEXALIGNOF(type) _Alignof (type) +#endif + +/* Yield a properly aligned upper bound on the size of a struct of + type TYPE with a flexible array member named MEMBER that is + followed by N bytes of other data. The result is suitable as an + argument to malloc. For example: + + struct s { int a; char d[FLEXIBLE_ARRAY_MEMBER]; }; + struct s *p = malloc (FLEXSIZEOF (struct s, d, n * sizeof (char))); + + FLEXSIZEOF (TYPE, MEMBER, N) is not simply (sizeof (TYPE) + N), + since FLEXIBLE_ARRAY_MEMBER may be 1 on pre-C11 platforms. Nor is + it simply (offsetof (TYPE, MEMBER) + N), as that might yield a size + that causes malloc to yield a pointer that is not properly aligned + for TYPE; for example, if sizeof (int) == alignof (int) == 4, + malloc (offsetof (struct s, d) + 3 * sizeof (char)) is equivalent + to malloc (7) and might yield a pointer that is not a multiple of 4 + (which means the pointer is not properly aligned for struct s), + whereas malloc (FLEXSIZEOF (struct s, d, 3 * sizeof (char))) is + equivalent to malloc (8) and must yield a pointer that is a + multiple of 4. + + Yield a value less than N if and only if arithmetic overflow occurs. */ + +#define FLEXSIZEOF(type, member, n) \ + ((offsetof (type, member) + FLEXALIGNOF (type) - 1 + (n)) \ + & ~ (FLEXALIGNOF (type) - 1)) + +/* Yield a properly aligned upper bound on the size of a struct of + type TYPE with a flexible array member named MEMBER that has N + elements. The result is suitable as an argument to malloc. + For example: + + struct s { int a; double d[FLEXIBLE_ARRAY_MEMBER]; }; + struct s *p = malloc (FLEXNSIZEOF (struct s, d, n)); + */ +#define FLEXNSIZEOF(type, member, n) \ + FLEXSIZEOF (type, member, (n) * sizeof (((type *) 0)->member[0])) diff --git a/include/fts.h b/include/fts.h index ea36a9b9be..972884647b 100644 --- a/include/fts.h +++ b/include/fts.h @@ -17,6 +17,13 @@ typedef struct int fts_nitems; int (*fts_compar) (const void *, const void *); int fts_options; + int fts_cwd_fd; + struct hash_table *fts_leaf_optimization_works_ht; + union { + struct hash_table *ht; + struct cycle_check_state *state; + } fts_cycle; + __I_ring fts_fd_ring; } FTS64_TIME64; typedef struct _ftsent64_time64 diff --git a/include/hash.h b/include/hash.h new file mode 100644 index 0000000000..d3c80aeedf --- /dev/null +++ b/include/hash.h @@ -0,0 +1,331 @@ +/* hash - hashing table processing. + Copyright (C) 1998-1999, 2001, 2003, 2009-2026 Free Software Foundation, + Inc. + Written by Jim Meyering , 1998. + + 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 . */ + +/* A generic hash table package. */ + +/* Make sure USE_OBSTACK is defined to 1 if you want the allocator to use + obstacks instead of malloc, and recompile 'hash.c' with same setting. */ + +#ifndef HASH_H_ +# define HASH_H_ + +/* This file uses _GL_ATTRIBUTE_DEALLOC, _GL_ATTRIBUTE_DEPRECATED, + _GL_ATTRIBUTE_MALLOC, _GL_ATTRIBUTE_NODISCARD, _GL_ATTRIBUTE_PURE, + _GL_ATTRIBUTE_RETURNS_NONNULL. */ +#if !_LIBC && !_GL_CONFIG_H_INCLUDED + #error "Please include config.h first." +#endif + +# include +# include + +# ifdef _LIBC +# define _GL_ATTRIBUTE_NODISCARD __attribute_warn_unused_result__ +# define _GL_ATTRIBUTE_MALLOC __attribute_malloc__ +# define _GL_ATTRIBUTE_DEALLOC(__a, __b) __attr_dealloc (__a, __b) +# define _GL_ATTRIBUTE_RETURNS_NONNULL __returns_nonnull +# define GNULIB_HASHCODE_STRING1 0 +# define hash_get_n_buckets __hash_get_n_buckets +# define hash_get_n_buckets_used __hash_get_n_buckets_used +# define hash_get_n_entries __hash_get_n_entries +# define hash_get_max_bucket_length __hash_get_max_bucket_length +# define hash_table_ok __hash_table_ok +# define hash_print_statistics __hash_print_statistics +# define hash_lookup __hash_lookup +# define hash_get_first __hash_get_first +# define hash_get_next __hash_get_next +# define hash_get_entries __hash_get_entries +# define hash_do_for_each __hash_do_for_each +# define hash_reset_tuning __hash_reset_tuning +# define hash_free __hash_free +# define hash_initialize __hash_initialize +# define hash_xinitialize __hash_xinitialize +# define hash_clear __hash_clear +# define hash_rehash __hash_rehash +# define hash_insert __hash_insert +# define hash_xinsert __hash_xinsert +# define hash_insert_if_absent __hash_insert_if_absent +# define hash_remove __hash_remove +# else +# define attribute_hidden +# endif + +# ifdef __cplusplus +extern "C" { +# endif + +struct hash_tuning + { + /* This structure is mainly used for 'hash_initialize', see the block + documentation of 'hash_reset_tuning' for more complete comments. */ + + float shrink_threshold; /* ratio of used buckets to trigger a shrink */ + float shrink_factor; /* ratio of new smaller size to original size */ + float growth_threshold; /* ratio of used buckets to trigger a growth */ + float growth_factor; /* ratio of new bigger size to original size */ + bool is_n_buckets; /* if CANDIDATE really means table size */ + }; + +typedef struct hash_tuning Hash_tuning; + +struct hash_table; + +typedef struct hash_table Hash_table; + +/* + * Information and lookup. + */ + +/* The following few functions provide information about the overall hash + table organization: the number of entries, number of buckets and maximum + length of buckets. */ + +/* Return the number of buckets in the hash table. The table size, the total + number of buckets (used plus unused), or the maximum number of slots, are + the same quantity. */ +extern size_t hash_get_n_buckets (const Hash_table *table) + _GL_ATTRIBUTE_PURE attribute_hidden; + +/* Return the number of slots in use (non-empty buckets). */ +extern size_t hash_get_n_buckets_used (const Hash_table *table) + _GL_ATTRIBUTE_PURE attribute_hidden; + +/* Return the number of active entries. */ +extern size_t hash_get_n_entries (const Hash_table *table) + _GL_ATTRIBUTE_PURE attribute_hidden; + +/* Return the length of the longest chain (bucket). */ +extern size_t hash_get_max_bucket_length (const Hash_table *table) + _GL_ATTRIBUTE_PURE attribute_hidden; + +/* Do a mild validation of a hash table, by traversing it and checking two + statistics. */ +extern bool hash_table_ok (const Hash_table *table) + _GL_ATTRIBUTE_PURE attribute_hidden; + +extern void hash_print_statistics (const Hash_table *table, FILE *stream) + attribute_hidden; + +/* If ENTRY matches an entry already in the hash table, return the + entry from the table. Otherwise, return NULL. */ +extern void *hash_lookup (const Hash_table *table, const void *entry) + attribute_hidden; + +/* + * Walking. + */ + +/* The functions in this page traverse the hash table and process the + contained entries. For the traversal to work properly, the hash table + should not be resized nor modified while any particular entry is being + processed. In particular, entries should not be added, and an entry + may be removed only if there is no shrink threshold and the entry being + removed has already been passed to hash_get_next. */ + +/* Return the first data in the table, or NULL if the table is empty. */ +extern void *hash_get_first (const Hash_table *table) + _GL_ATTRIBUTE_PURE attribute_hidden; + +/* Return the user data for the entry following ENTRY, where ENTRY has been + returned by a previous call to either 'hash_get_first' or 'hash_get_next'. + Return NULL if there are no more entries. */ +extern void *hash_get_next (const Hash_table *table, const void *entry) + attribute_hidden; + +/* Fill BUFFER with pointers to active user entries in the hash table, then + return the number of pointers copied. Do not copy more than BUFFER_SIZE + pointers. */ +extern size_t hash_get_entries (const Hash_table *table, void **buffer, + size_t buffer_size) + attribute_hidden; + +typedef bool (*Hash_processor) (void *entry, void *processor_data); + +/* Call a PROCESSOR function for each entry of a hash table, and return the + number of entries for which the processor function returned success. A + pointer to some PROCESSOR_DATA which will be made available to each call to + the processor function. The PROCESSOR accepts two arguments: the first is + the user entry being walked into, the second is the value of PROCESSOR_DATA + as received. The walking continue for as long as the PROCESSOR function + returns nonzero. When it returns zero, the walking is interrupted. */ +extern size_t hash_do_for_each (const Hash_table *table, + Hash_processor processor, void *processor_data) + attribute_hidden; + +/* Return a hash code of ENTRY, in the range 0..TABLE_SIZE-1. + This hash code function must have the property that if the comparator of + ENTRY1 and ENTRY2 returns true, the hasher returns the same value for ENTRY1 + and for ENTRY2. + The hash code function typically computes an unsigned integer and at the end + performs a % TABLE_SIZE modulo operation. This modulo operation is performed + as part of this hash code function, not by the caller, because in some cases + the unsigned integer will be a 'size_t', in other cases an 'uintmax_t' or + even larger. */ +typedef size_t (*Hash_hasher) (const void *entry, size_t table_size); + +/* Compare two entries, ENTRY1 (being looked up or being inserted) and + ENTRY2 (already in the table) for equality. Return true for equal, + false otherwise. */ +typedef bool (*Hash_comparator) (const void *entry1, const void *entry2); + +/* This function is invoked when an ENTRY is removed from the hash table. */ +typedef void (*Hash_data_freer) (void *entry); + +/* + * Allocation and clean-up. + */ + +extern void hash_reset_tuning (Hash_tuning *tuning) + attribute_hidden; + +/* Reclaim all storage associated with a hash table. If a data_freer + function has been supplied by the user when the hash table was created, + this function applies it to the data of each entry before freeing that + entry. This function preserves errno, like 'free'. */ +extern void hash_free (Hash_table *table) + attribute_hidden; + +/* Allocate and return a new hash table, or NULL upon failure. The initial + number of buckets is automatically selected so as to _guarantee_ that you + may insert at least CANDIDATE different user entries before any growth of + the hash table size occurs. So, if have a reasonably tight a-priori upper + bound on the number of entries you intend to insert in the hash table, you + may save some table memory and insertion time, by specifying it here. If + the IS_N_BUCKETS field of the TUNING structure is true, the CANDIDATE + argument has its meaning changed to the wanted number of buckets. + + TUNING points to a structure of user-supplied values, in case some fine + tuning is wanted over the default behavior of the hasher. If TUNING is + NULL, the default tuning parameters are used instead. If TUNING is + provided but the values requested are out of bounds or might cause + rounding errors, return NULL. + + The user-supplied HASHER function, when not NULL, accepts two + arguments ENTRY and TABLE_SIZE. It computes, by hashing ENTRY contents, a + slot number for that entry which should be in the range 0..TABLE_SIZE-1. + This slot number is then returned. + + The user-supplied COMPARATOR function, when not NULL, accepts two + arguments pointing to user data, it then returns true for a pair of entries + that compare equal, or false otherwise. This function is internally called + on entries which are already known to hash to the same bucket index, + but which are distinct pointers. + + The user-supplied DATA_FREER function, when not NULL, may be later called + with the user data as an argument, just before the entry containing the + data gets freed. This happens from within 'hash_free' or 'hash_clear'. + You should specify this function only if you want these functions to free + all of your 'data' data. This is typically the case when your data is + simply an auxiliary struct that you have malloc'd to aggregate several + values. + + Set errno on failure; otherwise errno is unspecified. */ +_GL_ATTRIBUTE_NODISCARD +extern Hash_table *hash_initialize (size_t candidate, + const Hash_tuning *tuning, + Hash_hasher hasher, + Hash_comparator comparator, + Hash_data_freer data_freer) + _GL_ATTRIBUTE_MALLOC _GL_ATTRIBUTE_DEALLOC (hash_free, 1) + attribute_hidden; + +/* Like hash_initialize, but invokes xalloc_die instead of returning NULL. */ +/* This function is defined by module 'xhash'. */ +_GL_ATTRIBUTE_NODISCARD +extern Hash_table *hash_xinitialize (size_t candidate, + const Hash_tuning *tuning, + Hash_hasher hasher, + Hash_comparator comparator, + Hash_data_freer data_freer) + _GL_ATTRIBUTE_MALLOC _GL_ATTRIBUTE_DEALLOC (hash_free, 1) + _GL_ATTRIBUTE_RETURNS_NONNULL + attribute_hidden; + +/* Make all buckets empty, placing any chained entries on the free list. + Apply the user-specified function data_freer (if any) to the data of any + affected entries. */ +extern void hash_clear (Hash_table *table) + attribute_hidden;; + +/* + * Insertion and deletion. + */ + +/* For an already existing hash table, change the number of buckets through + specifying CANDIDATE. The contents of the hash table are preserved. The + new number of buckets is automatically selected so as to _guarantee_ that + the table may receive at least CANDIDATE different user entries, including + those already in the table, before any other growth of the hash table size + occurs. If TUNING->IS_N_BUCKETS is true, then CANDIDATE specifies the + exact number of buckets desired. Return true iff the rehash succeeded, + false (setting errno) otherwise. */ +_GL_ATTRIBUTE_NODISCARD +extern bool hash_rehash (Hash_table *table, size_t candidate) + attribute_hidden; + +/* If ENTRY matches an entry already in the hash table, return the pointer + to the entry from the table. Otherwise, insert ENTRY and return ENTRY. + Return NULL (setting errno) if the storage required for insertion + cannot be allocated. This implementation does not support + duplicate entries or insertion of NULL. */ +_GL_ATTRIBUTE_NODISCARD +extern void *hash_insert (Hash_table *table, const void *entry) + attribute_hidden; + +/* Same as hash_insert, but invokes xalloc_die instead of returning NULL. */ +/* This function is defined by module 'xhash'. */ +extern void *hash_xinsert (Hash_table *table, const void *entry) + attribute_hidden; + +/* Insert ENTRY into hash TABLE if there is not already a matching entry. + + Return -1 (setting errno) upon memory allocation failure. + Return 1 if insertion succeeded. + Return 0 if there is already a matching entry in the table, + and in that case, if MATCHED_ENT is non-NULL, set *MATCHED_ENT + to that entry. + + This interface is easier to use than hash_insert when you must + distinguish between the latter two cases. More importantly, + hash_insert is unusable for some types of ENTRY values. When using + hash_insert, the only way to distinguish those cases is to compare + the return value and ENTRY. That works only when you can have two + different ENTRY values that point to data that compares "equal". Thus, + when the ENTRY value is a simple scalar, you must use + hash_insert_if_absent. ENTRY must not be NULL. */ +extern int hash_insert_if_absent (Hash_table *table, const void *entry, + const void **matched_ent) + attribute_hidden; + +/* If ENTRY is already in the table, remove it and return the just-deleted + data (the user may want to deallocate its storage). If ENTRY is not in the + table, don't modify the table and return NULL. */ +extern void *hash_remove (Hash_table *table, const void *entry) + attribute_hidden; + + +# if GNULIB_HASHCODE_STRING1 +/* Include declarations of module 'hashcode-string1'. */ +# include "hashcode-string1.h" +# endif + +# ifdef __cplusplus +} +# endif + +#endif diff --git a/include/next-prime.h b/include/next-prime.h new file mode 100644 index 0000000000..2ee965982b --- /dev/null +++ b/include/next-prime.h @@ -0,0 +1,47 @@ +/* Finding the next prime >= a given small integer. + Copyright (C) 1995-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 . */ + +#ifndef _GL_NEXT_PRIME_H +#define _GL_NEXT_PRIME_H + +/* This file uses _GL_ATTRIBUTE_CONST. */ +#if !_LIBC && !_GL_CONFIG_H_INCLUDED + #error "Please include config.h first." +#endif + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _LIBC +# define next_prime __next_prime +#else +# define attribute_hidden +#endif + +/* Round a given CANDIDATE number up to the nearest prime, and return that + prime. Primes lower than 10 are merely skipped. */ +extern size_t _GL_ATTRIBUTE_CONST next_prime (size_t candidate) + attribute_hidden;; + + +#ifdef __cplusplus +} +#endif + +#endif /* _GL_NEXT_PRIME_H */ diff --git a/include/xalloc-oversized.h b/include/xalloc-oversized.h new file mode 100644 index 0000000000..cecdaec51e --- /dev/null +++ b/include/xalloc-oversized.h @@ -0,0 +1,65 @@ +/* xalloc-oversized.h -- memory allocation size checking + + Copyright (C) 1990-2000, 2003-2004, 2006-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 . */ + +#ifndef XALLOC_OVERSIZED_H_ +#define XALLOC_OVERSIZED_H_ + +#include +#include + +/* True if N * S does not fit into both ptrdiff_t and size_t. + N and S should be nonnegative and free of side effects. + This expands to a constant expression if N and S are both constants. + By gnulib convention, SIZE_MAX represents overflow in size_t + calculations, so the conservative size_t-based dividend to use here + is SIZE_MAX - 1. */ +#define __xalloc_oversized(n, s) \ + ((s) != 0 \ + && (PTRDIFF_MAX < SIZE_MAX ? PTRDIFF_MAX : SIZE_MAX - 1) / (s) < (n)) + +/* Return 1 if and only if an array of N objects, each of size S, + cannot exist reliably because its total size in bytes would exceed + MIN (PTRDIFF_MAX, SIZE_MAX - 1). + + N and S should be nonnegative and free of side effects. + + Warning: (xalloc_oversized (N, S) ? NULL : malloc (N * S)) can + misbehave if N and S are both narrower than ptrdiff_t and size_t, + and can be rewritten as (xalloc_oversized (N, S) ? NULL + : malloc (N * (size_t) S)). + + This is a macro, not a function, so that it works even if an + argument exceeds MAX (PTRDIFF_MAX, SIZE_MAX). */ +#if 7 <= __GNUC__ && !defined __clang__ && PTRDIFF_MAX < SIZE_MAX +# define xalloc_oversized(n, s) \ + __builtin_mul_overflow_p (n, s, (ptrdiff_t) 1) +#elif 5 <= __GNUC__ && !defined __clang__ && !defined __ICC \ + && PTRDIFF_MAX < SIZE_MAX +# define xalloc_oversized(n, s) \ + (__builtin_constant_p (n) && __builtin_constant_p (s) \ + ? __xalloc_oversized (n, s) \ + : __extension__ \ + ({ ptrdiff_t __xalloc_count; \ + __builtin_mul_overflow (n, s, &__xalloc_count); })) + +/* Other compilers use integer division; this may be slower but is + more portable. */ +#else +# define xalloc_oversized(n, s) __xalloc_oversized (n, s) +#endif + +#endif /* !XALLOC_OVERSIZED_H_ */ diff --git a/io/Makefile b/io/Makefile index 80e50578b2..2a5b4dcb65 100644 --- a/io/Makefile +++ b/io/Makefile @@ -197,7 +197,9 @@ tests := \ tst-fcntl-lock-lfs \ tst-fstatat \ tst-fts \ + tst-fts-bz22944 \ tst-fts-lfs \ + tst-fts-newflags \ tst-ftw-bz26353 \ tst-ftw-bz28126 \ tst-ftw-lnk \ diff --git a/io/cycle-check.c b/io/cycle-check.c new file mode 100644 index 0000000000..3a20d28aec --- /dev/null +++ b/io/cycle-check.c @@ -0,0 +1,85 @@ + +/* help detect directory cycles efficiently + + Copyright (C) 2003-2006, 2009-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 . */ + +/* Written by Jim Meyering */ + +#include + +#include "cycle-check.h" + +#include +#include +#include +#include + +#include "assure.h" + +#define CC_MAGIC 9827862 + +/* Return true if I is a power of 2, or is zero. */ + +static bool +is_zero_or_power_of_two (uintmax_t i) +{ + return (i & (i - 1)) == 0; +} + +INTERNAL_DEF void +cycle_check_init (struct cycle_check_state *state) +{ + state->chdir_counter = 0; + state->magic = CC_MAGIC; +} + +/* In traversing a directory hierarchy, call this function once for each + descending chdir call, with SB corresponding to the chdir operand. + If SB corresponds to a directory that has already been seen, + return true to indicate that there is a directory cycle. + Note that this is done "lazily", which means that some of + the directories in the cycle may be processed twice before + the cycle is detected. */ + +INTERNAL_DEF bool +cycle_check (struct cycle_check_state *state, struct STRUCT_STAT const *sb) +{ + assure (state->magic == CC_MAGIC); + + /* If the current directory ever happens to be the same + as the one we last recorded for the cycle detection, + then it's obviously part of a cycle. */ + if (state->chdir_counter && SAME_INODE (*sb, state->dev_ino)) + return true; + + /* If the number of "descending" chdir calls is a power of two, + record the dev/ino of the current directory. */ + if (is_zero_or_power_of_two (++(state->chdir_counter))) + { + /* On all architectures that we know about, if the counter + overflows then there is a directory cycle here somewhere, + even if we haven't detected it yet. Typically this happens + only after the counter is incremented 2**64 times, so it's a + fairly theoretical point. */ + if (state->chdir_counter == 0) + return true; + + state->dev_ino.st_dev = sb->st_dev; + state->dev_ino.st_ino = sb->st_ino; + } + + return false; +} diff --git a/io/cycle-check.h b/io/cycle-check.h new file mode 100644 index 0000000000..0152992ced --- /dev/null +++ b/io/cycle-check.h @@ -0,0 +1,70 @@ + +/* help detect directory cycles efficiently + + Copyright (C) 2003-2004, 2006, 2009-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 . */ + +/* Written by Jim Meyering */ + +#ifndef CYCLE_CHECK_H +#define CYCLE_CHECK_H 1 + +#include +#include "dev-ino.h" +#include "same-inode.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if _LIBC +# define cycle_check_init __cycle_check_init +# define cycle_check __cycle_check +# define INTERNAL_DEF static +#else +# define INTERNAL_DEF +#endif + +struct cycle_check_state +{ + struct dev_ino dev_ino; + uintmax_t chdir_counter; + int magic; +}; + +INTERNAL_DEF void cycle_check_init (struct cycle_check_state *state); +INTERNAL_DEF bool cycle_check (struct cycle_check_state *state, + struct STRUCT_STAT const *sb); + +#define CYCLE_CHECK_REFLECT_CHDIR_UP(State, SB_dir, SB_subdir) \ + do \ + { \ + /* You must call cycle_check at least once before using this macro. */ \ + if ((State)->chdir_counter == 0) \ + abort (); \ + if (SAME_INODE ((State)->dev_ino, SB_subdir)) \ + { \ + (State)->dev_ino.st_dev = (SB_dir).st_dev; \ + (State)->dev_ino.st_ino = (SB_dir).st_ino; \ + } \ + } \ + while (0) + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/io/dev-ino.h b/io/dev-ino.h new file mode 100644 index 0000000000..9b1878c79f --- /dev/null +++ b/io/dev-ino.h @@ -0,0 +1,44 @@ +/* A simple (device, inode) struct. + Copyright (C) 2003-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 . */ + +/* Written by Jim Meyering, 2003. */ + +#ifndef DEV_INO_H +#define DEV_INO_H 1 + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef INO_T +#define INO_T ino_t +#endif + +struct dev_ino +{ + INO_T st_ino; + dev_t st_dev; +}; + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/io/fts-cycle.c b/io/fts-cycle.c new file mode 100644 index 0000000000..6bbc9f9446 --- /dev/null +++ b/io/fts-cycle.c @@ -0,0 +1,162 @@ +/* Detect cycles in file tree walks. + + Copyright (C) 2003-2006, 2009-2026 Free Software Foundation, Inc. + + Written by Jim Meyering. + + 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 . */ + +#include "cycle-check.h" +#include "hash.h" + +#ifndef FTS_OPEN +# define FTSOBJ FTS +# define FTSENTRY FTSENT +#endif + +/* Use each of these to map a device/inode pair to an FTSENT. */ +struct Active_dir +{ + dev_t dev; + INO_T ino; + FTSENTRY *fts_ent; +}; + +static bool +AD_compare (void const *x, void const *y) +{ + struct Active_dir const *ax = x; + struct Active_dir const *ay = y; + return ax->ino == ay->ino + && ax->dev == ay->dev; +} + +static size_t +AD_hash (void const *x, size_t table_size) +{ + struct Active_dir const *ax = x; + return (uintmax_t) ax->ino % table_size; +} + +/* Set up the cycle-detection machinery. */ + +static bool +setup_dir (FTSOBJ *fts) +{ + if (fts->fts_options & (FTS_TIGHT_CYCLE_CHECK | FTS_LOGICAL)) + { + enum { HT_INITIAL_SIZE = 31 }; + fts->fts_cycle.ht = hash_initialize (HT_INITIAL_SIZE, NULL, AD_hash, + AD_compare, free); + if (! fts->fts_cycle.ht) + return false; + } + else + { + fts->fts_cycle.state = malloc (sizeof *fts->fts_cycle.state); + if (! fts->fts_cycle.state) + return false; + cycle_check_init (fts->fts_cycle.state); + } + + return true; +} + +/* Enter a directory during a file tree walk. */ + +static bool +enter_dir (FTSOBJ *fts, FTSENTRY *ent) +{ + if (fts->fts_options & (FTS_TIGHT_CYCLE_CHECK | FTS_LOGICAL)) + { + struct Active_dir *ad = malloc (sizeof *ad); + if (!ad) + return false; + + struct STRUCT_STAT const *st = ent->fts_statp; + ad->dev = st->st_dev; + ad->ino = st->st_ino; + ad->fts_ent = ent; + + /* See if we've already encountered this directory. + This can happen when following symlinks as well as + with a corrupted directory hierarchy. */ + struct Active_dir *ad_from_table = hash_insert (fts->fts_cycle.ht, ad); + + if (ad_from_table != ad) + { + free (ad); + if (!ad_from_table) + return false; + + /* There was an entry with matching dev/inode already in the table. + Record the fact that we've found a cycle. */ + ent->fts_cycle = ad_from_table->fts_ent; + ent->fts_info = FTS_DC; + } + } + else + { + if (cycle_check (fts->fts_cycle.state, ent->fts_statp)) + { + /* FIXME: setting fts_cycle like this isn't proper. + To do what the documentation requires, we'd have to + go around the cycle again and find the right entry. + But no callers in coreutils use the fts_cycle member. */ + ent->fts_cycle = ent; + ent->fts_info = FTS_DC; + } + } + + return true; +} + +/* Leave a directory during a file tree walk. */ + +static void +leave_dir (FTSOBJ *fts, FTSENTRY *ent) +{ + struct STRUCT_STAT const *st = ent->fts_statp; + if (fts->fts_options & (FTS_TIGHT_CYCLE_CHECK | FTS_LOGICAL)) + { + struct Active_dir obj; + obj.dev = st->st_dev; + obj.ino = st->st_ino; + void *found = hash_remove (fts->fts_cycle.ht, &obj); + if (!found) + abort (); + free (found); + } + else + { + FTSENTRY *parent = ent->fts_parent; + if (parent != NULL && 0 <= parent->fts_level) + CYCLE_CHECK_REFLECT_CHDIR_UP (fts->fts_cycle.state, + *(parent->fts_statp), *st); + } +} + +/* Free any memory used for cycle detection. */ + +static void +free_dir (FTSOBJ *sp) +{ + if (sp->fts_options & (FTS_TIGHT_CYCLE_CHECK | FTS_LOGICAL)) + { + if (sp->fts_cycle.ht) + hash_free (sp->fts_cycle.ht); + } + else + free (sp->fts_cycle.state); +} diff --git a/io/fts.c b/io/fts.c index 27a15b1104..3825f0aeb4 100644 --- a/io/fts.c +++ b/io/fts.c @@ -1,24 +1,23 @@ -/* File tree traversal functions. - Copyright (C) 1994-2026 Free Software Foundation, Inc. - This file is part of the GNU C Library. +/* Traverse a file hierarchy. - 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. + Copyright (C) 2004-2026 Free Software Foundation, Inc. - The GNU C Library is distributed in the hope that it will be useful, + 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. + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. - You should have received a copy of the GNU Lesser General Public - License along with the GNU C Library; if not, see - . */ + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ /*- * Copyright (c) 1990, 1993, 1994 - * The Regents of the University of California. All rights reserved. + * 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 @@ -32,7 +31,7 @@ * 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 + * 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 @@ -45,457 +44,1122 @@ * SUCH DAMAGE. */ -#if defined(LIBC_SCCS) && !defined(lint) -static char sccsid[] = "@(#)fts.c 8.6 (Berkeley) 8/14/94"; -#endif /* LIBC_SCCS and not lint */ +#include -#include -#include +#if defined LIBC_SCCS && !defined GCC_LINT && !defined lint +static char sccsid[] = "@(#)fts.c 8.6 (Berkeley) 8/14/94"; +#endif + +#if _LIBC +# include +#else +# include "fts_.h" +#endif +#if _LIBC || HAVE_SYS_PARAM_H +# include +#endif +#include #include -#include #include -#include +#include #include #include #include #include - -/* Largest alignment size needed, minus one. - Usually long double is the worst case. */ -#ifndef ALIGNBYTES -#define ALIGNBYTES (__alignof__ (long double) - 1) -#endif -/* Align P to that size. */ -#ifndef ALIGN -#define ALIGN(p) (((uintptr_t) (p) + ALIGNBYTES) & ~ALIGNBYTES) -#endif - - /* Support for the LFS API version. */ #ifndef FTS_OPEN -#define FTS_OPEN fts_open -#define FTS_CLOSE fts_close -#define FTS_READ fts_read -#define FTS_SET fts_set -#define FTS_CHILDREN fts_children +# define FTS_OPEN fts_open +# define FTS_CLOSE fts_close +# define FTS_READ fts_read +# define FTS_SET fts_set +# define FTS_CHILDREN fts_children # define FTSOBJ FTS # define FTSENTRY FTSENT # define INO_T ino_t # define STRUCT_STAT stat -# define STAT __stat -# define LSTAT __lstat # define FSTAT __fstat +# define FSTATAT __fstatat +# define STRUCT_STATFS statfs +# define FSTATFS __fstatfs #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 int fts_palloc (FTSOBJ *, size_t); -static FTSENTRY *fts_sort (FTSOBJ *, FTSENTRY *, int); -static u_short fts_stat (FTSOBJ *, FTSENTRY *, int); +#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 + +# 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 +#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) ({ __typeof__ (a) _a = (a); \ - __typeof__ (b) _b = (b); \ - _a > _b ? _a : _b; }) +# define MAX(a,b) ((a) > (b) ? (a) : (b)) #endif -#define ISDOT(a) (a[0] == '.' && (!a[1] || (a[1] == '.' && !a[2]))) +#ifndef SIZE_MAX +# define SIZE_MAX ((size_t) -1) +#endif -#define CLR(opt) (sp->fts_options &= ~(opt)) -#define ISSET(opt) (sp->fts_options & (opt)) -#define SET(opt) (sp->fts_options |= (opt)) +#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))) -#define FCHDIR(sp, fd) (!ISSET(FTS_NOCHDIR) && __fchdir(fd)) /* fts_build flags */ -#define BCHILD 1 /* fts_children */ -#define BNAMES 2 /* fts_children, names only */ -#define BREAD 3 /* fts_read */ +/* 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 +# include +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, int options, - int (*compar) (const FTSENTRY **, const FTSENTRY **)) +FTS_OPEN (char * const *argv, + register int options, + int (*compar) (const FTSENTRY **, const FTSENTRY **)) { - FTSOBJ *sp; - FTSENTRY *p, *root; - int nitems; - FTSENTRY *parent = NULL; - FTSENTRY *tmp; - - /* Options check. */ - if (options & ~FTS_OPTIONMASK) { - __set_errno (EINVAL); - return (NULL); - } - - /* Allocate/initialize the stream */ - if ((sp = malloc((u_int)sizeof(FTSOBJ))) == NULL) - return (NULL); - memset(sp, 0, sizeof(FTSOBJ)); - sp->fts_compar = (int (*) (const void *, const void *)) compar; - sp->fts_options = options; - - /* Logical walks turn on NOCHDIR; symbolic links are too hard. */ - if (ISSET(FTS_LOGICAL)) - SET(FTS_NOCHDIR); - - /* - * Start out with 1K of path space, and enough, in any case, - * to hold the user's paths. - */ -#ifndef MAXPATHLEN -#define MAXPATHLEN 1024 + /* 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 - size_t maxarglen = fts_maxarglen(argv); - if (fts_palloc(sp, MAX(maxarglen, MAXPATHLEN))) - goto mem1; - /* Allocate/initialize root's parent. */ - if (*argv != NULL) { - if ((parent = fts_alloc(sp, "", 0)) == NULL) - goto mem2; - parent->fts_level = FTS_ROOTPARENTLEVEL; - } + /* 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; - /* Allocate/initialize root(s). */ - for (root = NULL, nitems = 0; *argv != NULL; ++argv, ++nitems) { - /* Don't allow zero-length paths. */ - size_t len = strlen(*argv); - if (len == 0) { - __set_errno (ENOENT); - goto mem3; - } + /* Logical walks turn on NOCHDIR; symbolic links are too hard. */ + if (ISSET(FTS_LOGICAL)) { + SET(FTS_NOCHDIR); + CLR(FTS_CWDFD); + } - p = fts_alloc(sp, *argv, len); - p->fts_level = FTS_ROOTLEVEL; - p->fts_parent = parent; - p->fts_accpath = p->fts_name; - p->fts_info = fts_stat(sp, p, ISSET(FTS_COMFOLLOW)); + /* 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); + } + } - /* Command-line "." and ".." are real directories. */ - if (p->fts_info == FTS_DOT) - p->fts_info = FTS_D; + /* + * 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; + } - /* - * 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/initialize root's parent. */ + FTSENTRY *parent = NULL; + if (*argv != NULL) { + if ((parent = fts_alloc(sp, "", 0)) == NULL) + goto mem2; + parent->fts_level = FTS_ROOTPARENTLEVEL; + } - /* - * 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; + /* 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)); - /* - * 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 paths, - * 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) - && (sp->fts_rfd = __open(".", O_RDONLY, 0)) < 0) - SET(FTS_NOCHDIR); + /* 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); - return (sp); + 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; + } -mem3: fts_lfree(root); - free(parent); -mem2: free(sp->fts_path); -mem1: free(sp); - return (NULL); + 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 -fts_load (FTSOBJ *sp, FTSENTRY *p) +internal_function +fts_load (FTSOBJ *sp, register FTSENTRY *p) { - int len; - char *cp; - - /* - * 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 path will fit. - */ - len = p->fts_pathlen = p->fts_namelen; - memmove(sp->fts_path, p->fts_name, len + 1); - if ((cp = strrchr(p->fts_name, '/')) && (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; - sp->fts_dev = p->fts_dev; + /* + * 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) { - FTSENTRY *freep, *p; - int saved_errno; + /* + * 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)); + } - /* - * 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) { - for (p = sp->fts_cur; p->fts_level >= FTS_ROOTLEVEL;) { - freep = p; - p = p->fts_link != NULL ? p->fts_link : p->fts_parent; - free(freep); - } - free(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); - /* Free up child linked list, sort array, path 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; - /* Return to original directory, save errno if necessary. */ - if (!ISSET(FTS_NOCHDIR)) { - saved_errno = __fchdir(sp->fts_rfd) ? errno : 0; - (void)__close(sp->fts_rfd); + /* 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; + } - /* Set errno and return. */ - if (saved_errno != 0) { - /* Free up the stream pointer. */ - free(sp); - __set_errno (saved_errno); - return (-1); - } - } + fd_ring_clear (&sp->fts_fd_ring); - /* Free up the stream pointer. */ - free(sp); - return (0); + 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 + +/* 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 + . */ + 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 . */ + FALLTHROUGH; + case S_MAGIC_CIFS: + /* Leaf optimization causes 'find' to abort. See + . */ + FALLTHROUGH; + case S_MAGIC_NFS: + /* NFS provides usable dirent.d_type but not necessarily for all entries + of large directories, so as per + NFS should return true. However st_nlink values are not accurate on + all implementations as per . */ + FALLTHROUGH; + case S_MAGIC_PROC: + /* Per /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 path so that slashes aren't - * appended which would cause paths to be written as "....//foo". + * 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) +#define NAPPEND(p) \ + (p->fts_path[p->fts_pathlen - 1] == '/' \ + ? p->fts_pathlen - 1 : p->fts_pathlen) FTSENTRY * FTS_READ (FTSOBJ *sp) { - FTSENTRY *p, *tmp; - int instr; - char *t; - int saved_errno; + /* If finished or unrecoverable error, return NULL. */ + if (sp->fts_cur == NULL || ISSET(FTS_STOP)) + return (NULL); - /* 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; - /* Set current node pointer. */ - p = sp->fts_cur; + /* Save and zero out user instructions. */ + register unsigned short int instr = p->fts_instr; + p->fts_instr = FTS_NOINSTR; - /* Save and zero out user instructions. */ - 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)); - /* Any type of file may be re-visited; re-stat and re-turn. */ - if (instr == FTS_AGAIN) { - p->fts_info = fts_stat(sp, p, 0); - return (p); - } + /* + * 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; + } - /* - * 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, 1); - if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) { - if ((p->fts_symfd = __open(".", O_RDONLY, 0)) < 0) { - p->fts_errno = errno; - p->fts_info = FTS_ERR; - } else - p->fts_flags |= FTS_SYMFOLLOW; - } - return (p); - } + /* 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); + } - /* 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_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; - 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; + } - /* 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; + } - /* - * 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); - return (p); - } - p = sp->fts_child; - sp->fts_child = NULL; - sp->fts_cur = p; - goto name; - } + /* Move to the next node on this level. */ +next: ; + register FTSENTRY *tmp = p; - /* Move to the next node on this level. */ -next: tmp = p; - if ((p = p->fts_link) != NULL) { - sp->fts_cur = p; - free(tmp); + /* 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 reached the top, return to the original directory (or - * the root of the tree), and load the paths for the next root. - */ - if (p->fts_level == FTS_ROOTLEVEL) { - if (FCHDIR(sp, sp->fts_rfd)) { - SET(FTS_STOP); - return (NULL); - } - fts_load(sp, p); - return p; - } + if ((p = fts_build (sp, BREAD)) == NULL) + { + if (ISSET(FTS_STOP)) + return NULL; + goto cd_dot_dot; + } - /* - * 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, 1); - if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) { - if ((p->fts_symfd = - __open(".", O_RDONLY, 0)) < 0) { - p->fts_errno = errno; - p->fts_info = FTS_ERR; - } else - p->fts_flags |= FTS_SYMFOLLOW; - } - p->fts_instr = FTS_NOINSTR; - } + free(FTSENT_WRAPPER(tmp)); + goto name; + } -name: t = sp->fts_path + NAPPEND(p->fts_parent); - *t++ = '/'; - memmove(t, p->fts_name, p->fts_namelen + 1); - return p; - } + if ((p = p->fts_link) != NULL) { + sp->fts_cur = p; + free(FTSENT_WRAPPER(tmp)); - /* Move up to the parent node. */ - p = tmp->fts_parent; - sp->fts_cur = p; - free(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; + } - 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(p); - __set_errno (0); - return (sp->fts_cur = NULL); - } + /* + * 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; + } - /* NUL terminate the pathname. */ - sp->fts_path[p->fts_pathlen] = '\0'; +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); + } - /* - * Return to the parent directory. If at a root node or came through - * a symlink, go back through the file descriptor. Otherwise, cd up - * one directory. - */ - if (p->fts_level == FTS_ROOTLEVEL) { - if (FCHDIR(sp, sp->fts_rfd)) { - SET(FTS_STOP); - return (NULL); - } - } else if (p->fts_flags & FTS_SYMFOLLOW) { - if (FCHDIR(sp, p->fts_symfd)) { - saved_errno = errno; - (void)__close(p->fts_symfd); - __set_errno (saved_errno); - SET(FTS_STOP); - return (NULL); - } - (void)__close(p->fts_symfd); - } else if (!(p->fts_flags & FTS_DONTCHDIR) && - fts_safe_changedir(sp, p->fts_parent, -1, "..")) { - SET(FTS_STOP); - return (NULL); - } - p->fts_info = p->fts_errno ? FTS_ERR : FTS_DP; - return p; + /* 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; } /* @@ -506,93 +1170,157 @@ name: t = sp->fts_path + NAPPEND(p->fts_parent); */ /* ARGSUSED */ int -FTS_SET (FTSOBJ *sp, FTSENTRY *p, int instr) +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); + 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) +FTS_CHILDREN (FTSOBJ *sp, int instr) { - FTSENTRY *p; - int fd; + if (instr != 0 && instr != FTS_NAMEONLY) { + __set_errno (EINVAL); + return (NULL); + } - if (instr != 0 && instr != FTS_NAMEONLY) { - __set_errno (EINVAL); - return (NULL); - } + /* Set current node pointer. */ + register FTSENTRY *p = sp->fts_cur; - /* Set current node pointer. */ - p = sp->fts_cur; + /* + * Errno set to 0 so user can distinguish empty directory from + * an error. + */ + __set_errno (0); - /* - * 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); - /* 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); - /* 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); - /* - * 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); - /* 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 (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)); - /* - * If using chdir on a relative path 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)); - - if ((fd = __open(".", O_RDONLY, 0)) < 0) - return (NULL); - sp->fts_child = fts_build(sp, instr); - if (__fchdir(fd)) - return (NULL); - (void)__close(fd); - return (sp->fts_child); + 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); } -static inline int -dirent_not_directory(const struct dirent *dp) +/* 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) { -#if defined DT_DIR && defined _DIRENT_HAVE_D_TYPE - return dp->d_type != DT_DIR && dp->d_type != DT_UNKNOWN; -#else - return 0; -#endif + 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 @@ -608,531 +1336,873 @@ dirent_not_directory(const struct dirent *dp) * been found, cutting the stat calls by about 2/3. */ static FTSENTRY * -fts_build (FTSOBJ *sp, int type) +internal_function +fts_build (register FTSOBJ *sp, int type) { - struct dirent *dp; - FTSENTRY *p, *head; - int nitems; - FTSENTRY *cur, *tail; - DIR *dirp; - void *oldaddr; - int cderrno, descend, len, level, nlinks, saved_errno, - nostat, doadjust; - size_t maxlen; - char *cp; + FTSENTRY *cur = sp->fts_cur; + bool continue_readdir = !!FTSENT_DIRP(cur); - /* Set current node pointer. */ - cur = sp->fts_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; - /* - * Open the directory for reading. If this fails, we're done. - * If being called from fts_read, set the fts_info field. - */ -#if defined FTS_WHITEOUT && 0 - if (ISSET(FTS_WHITEOUT)) - oflag = DTF_NODUP|DTF_REWIND; - else - oflag = DTF_HIDEW|DTF_NODUP|DTF_REWIND; -#else -# define __opendir2(path, flag) __opendir(path) + 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 ((dirp = __opendir2(cur->fts_accpath, oflag)) == NULL) { - if (type == BREAD) { - cur->fts_info = FTS_DNR; - cur->fts_errno = errno; - } - return (NULL); - } + 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; - /* - * Nlinks is the number of possible entries of type directory in the - * directory if we're cheating on stat calls, 0 if we're not doing - * any stat calls at all, -1 if we're doing stats on everything. - */ - if (type == BNAMES) { - nlinks = 0; - /* Be quiet about nostat, GCC. */ - nostat = 0; - } else if (ISSET(FTS_NOSTAT) && ISSET(FTS_PHYSICAL)) { - nlinks = cur->fts_nlink - (ISSET(FTS_SEEDOT) ? 0 : 2); - nostat = 1; - } else { - nlinks = -1; - nostat = 0; - } + 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; + } -#ifdef notdef - (void)printf("nlinks == %d (cur: %d)\n", nlinks, cur->fts_nlink); - (void)printf("NOSTAT %d PHYSICAL %d SEEDOT %d\n", - ISSET(FTS_NOSTAT), ISSET(FTS_PHYSICAL), ISSET(FTS_SEEDOT)); -#endif - /* - * 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 path - * 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. - */ - cderrno = 0; - if (nlinks || type == BREAD) { - if (fts_safe_changedir(sp, cur, dirfd(dirp), NULL)) { - if (nlinks && type == BREAD) - cur->fts_errno = errno; - cur->fts_flags |= FTS_DONTCHDIR; - descend = 0; - cderrno = errno; - (void)__closedir(dirp); - dirp = NULL; - } else - descend = 1; - } else - descend = 0; + 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; - /* - * Figure out the max file name length that can be stored in the - * current path -- the inner loop allocates more path as necessary. - * We really wouldn't have to do the maxlen calculations here, we - * could do them in fts_read before returning the path, 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 name into the path. - */ - len = NAPPEND(cur); - if (ISSET(FTS_NOCHDIR)) { - cp = sp->fts_path + len; - *cp++ = '/'; - } else { - /* GCC, you're too verbose. */ - cp = NULL; - } - len++; - maxlen = sp->fts_pathlen - len; + /* Store dirent.d_ino, in case we need to sort + entries before processing them. */ + p->fts_statp->st_ino = D_INO (dp); - level = cur->fts_level + 1; + /* 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; - /* Read the directory, attaching each entry to the `link' pointer. */ - doadjust = 0; - for (head = tail = NULL, nitems = 0; dirp && (dp = __readdir(dirp));) { - if (!ISSET(FTS_SEEDOT) && ISDOT(dp->d_name)) - continue; + 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); + } - if ((p = fts_alloc(sp, dp->d_name, _D_EXACT_NAMLEN (dp))) == NULL) - goto mem1; - if (_D_EXACT_NAMLEN (dp) >= maxlen) {/* include space for NUL */ - oldaddr = sp->fts_path; - if (fts_palloc(sp, _D_EXACT_NAMLEN (dp) + len + 1)) { - /* - * No more memory for path or structures. Save - * errno, free up the current structure and the - * structures already allocated. - */ -mem1: saved_errno = errno; - free(p); - fts_lfree(head); - (void)__closedir(dirp); - cur->fts_info = FTS_ERR; - SET(FTS_STOP); - __set_errno (saved_errno); - return (NULL); - } - /* Did realloc() change the pointer? */ - if (oldaddr != sp->fts_path) { - doadjust = 1; - if (ISSET(FTS_NOCHDIR)) - cp = sp->fts_path + len; - } - maxlen = sp->fts_pathlen - len; - } + /* 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 (len + _D_EXACT_NAMLEN (dp) >= USHRT_MAX) { - /* - * In an FTSENT, fts_pathlen is a u_short so it is - * possible to wraparound here. If we do, free up - * the current structure and the structures already - * allocated, then error out with ENAMETOOLONG. - */ - free(p); - fts_lfree(head); - (void)__closedir(dirp); - 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 = len + _D_EXACT_NAMLEN (dp); + /* 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. -#if defined FTS_WHITEOUT && 0 - if (dp->d_type == DT_WHT) - p->fts_flags |= FTS_ISW; -#endif + 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); - /* Unreachable code. cderrno is only ever set to a nonnull - value if dirp is closed at the same time. But then we - cannot enter this loop. */ - if (0 && cderrno) { - if (nlinks) { - p->fts_info = FTS_NS; - p->fts_errno = cderrno; - } else - p->fts_info = FTS_NSOK; - p->fts_accpath = cur->fts_accpath; - } else if (nlinks == 0 - || (nostat && dirent_not_directory(dp))) { - p->fts_accpath = - ISSET(FTS_NOCHDIR) ? p->fts_path : p->fts_name; - p->fts_info = FTS_NSOK; - } else { - /* 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; - /* Stat it. */ - p->fts_info = fts_stat(sp, p, 0); + ++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; + } + } - /* Decrement link count if applicable. */ - if (nlinks > 0 && (p->fts_info == FTS_D || - p->fts_info == FTS_DC || p->fts_info == FTS_DOT)) - --nlinks; - } + /* + * 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); - /* 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; - } - ++nitems; - } - if (dirp) - (void)__closedir(dirp); + /* + * 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 realloc() changed the address of the path, adjust the - * addresses for the rest of the tree and the dir list. - */ - if (doadjust) - fts_padjust(sp, head); + /* + * 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 not changing directories, reset the path back to original - * state. - */ - if (ISSET(FTS_NOCHDIR)) { - if (len == sp->fts_pathlen || nitems == 0) - --cp; - *cp = '\0'; - } + /* 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 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 path - * to an empty directory, we wind up here with no other way back. If - * can't get back, we're done. - */ - if (descend && (type == BCHILD || !nitems) && - (cur->fts_level == FTS_ROOTLEVEL ? - FCHDIR(sp, sp->fts_rfd) : - fts_safe_changedir(sp, cur->fts_parent, -1, ".."))) { - cur->fts_info = FTS_ERR; - SET(FTS_STOP); - 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; + } - /* If didn't find anything, return NULL. */ - if (!nitems) { - if (type == BREAD) - cur->fts_info = FTS_DP; - fts_lfree(head); - return (NULL); - } - - /* Sort the entries. */ - if (sp->fts_compar && nitems > 1) - head = fts_sort(sp, head, nitems); - return (head); + /* Sort the entries. */ + if (sp->fts_compar && nitems > 1) + head = fts_sort(sp, head, nitems); + return (head); } -static u_short -fts_stat (FTSOBJ *sp, FTSENTRY *p, int follow) +#if GNULIB_FTS_DEBUG + +struct devino { + intmax_t dev, ino; +}; +#define PRINT_DEVINO "(%jd,%jd)" + +static struct devino +getdevino (int fd) { - FTSENTRY *t; - dev_t dev; - INO_T ino; - struct STRUCT_STAT *sbp, sb; - int saved_errno; - - /* If user needs stat info, stat buffer already allocated. */ - sbp = ISSET(FTS_NOSTAT) ? &sb : p->fts_statp; - -#if defined FTS_WHITEOUT && 0 - /* check for whiteout */ - if (p->fts_flags & FTS_ISW) { - if (sbp != &sb) { - memset(sbp, '\0', sizeof (*sbp)); - sbp->st_mode = S_IFWHT; - } - return (FTS_W); - } -#endif - - /* - * If doing a logical walk, or application requested FTS_FOLLOW, do - * a stat(2). If that fails, check for a non-existent symlink. If - * fail, set the errno from the stat call. - */ - if (ISSET(FTS_LOGICAL) || follow) { - if (STAT(p->fts_accpath, sbp)) { - saved_errno = errno; - if (!LSTAT(p->fts_accpath, sbp)) { - __set_errno (0); - return (FTS_SLNONE); - } - p->fts_errno = saved_errno; - goto err; - } - } else if (LSTAT(p->fts_accpath, sbp)) { - p->fts_errno = errno; -err: memset(sbp, 0, sizeof(struct STRUCT_STAT)); - return (FTS_NS); - } - - if (S_ISDIR(sbp->st_mode)) { - /* - * Set the device/inode. Used to find cycles and check for - * crossing mount points. Also remember the link count, used - * in fts_build to limit the number of stat calls. It is - * understood that these fields are only referenced if fts_info - * is set to FTS_D. - */ - dev = p->fts_dev = sbp->st_dev; - ino = p->fts_ino = sbp->st_ino; - p->fts_nlink = sbp->st_nlink; - - if (ISDOT(p->fts_name)) - return (FTS_DOT); - - /* - * Cycle detection is done by brute force when the directory - * is first encountered. If the tree gets deep enough or the - * number of symbolic links to directories is high enough, - * something faster might be worthwhile. - */ - for (t = p->fts_parent; - t->fts_level >= FTS_ROOTLEVEL; t = t->fts_parent) - if (ino == t->fts_ino && dev == t->fts_dev) { - p->fts_cycle = t; - return (FTS_DC); - } - return (FTS_D); - } - if (S_ISLNK(sbp->st_mode)) - return (FTS_SL); - if (S_ISREG(sbp->st_mode)) - return (FTS_F); - return (FTS_DEFAULT); + 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 }); } -static FTSENTRY * -fts_sort (FTSOBJ *sp, FTSENTRY *head, int nitems) +/* 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) { - FTSENTRY **ap, *p; - - /* - * 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) { - FTSENTRY **a; - - sp->fts_nitems = nitems + 40; - if ((a = realloc(sp->fts_array, - (size_t)(sp->fts_nitems * sizeof(FTSENTRY *)))) == NULL) { - 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 *), sp->fts_compar); - for (head = *(ap = sp->fts_array); --nitems; ++ap) - ap[0]->fts_link = ap[1]; - ap[0]->fts_link = NULL; - return (head); + 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); } -static FTSENTRY * -fts_alloc (FTSOBJ *sp, const char *name, size_t namelen) +void +fts_cross_check (FTSOBJ const *sp) { - FTSENTRY *p; - size_t len; + if ( ! ISSET (FTS_TIGHT_CYCLE_CHECK)) + return; - /* - * The file name is a variable length array and no stat structure is - * necessary if the user has set the nostat bit. Allocate the FTSENT - * structure, the file name and the stat structure in one chunk, but - * be careful that the stat structure is reasonably aligned. Since the - * fts_name field is declared to be of size 1, the fts_name pointer is - * namelen + 2 before the first possible address of the stat structure. - */ - len = sizeof(FTSENTRY) + namelen; - if (!ISSET(FTS_NOSTAT)) - len += sizeof(struct STRUCT_STAT) + ALIGNBYTES; - if ((p = malloc(len)) == NULL) - return (NULL); + FTSENTRY const *ent = sp->fts_cur; - /* Copy the name and guarantee NUL termination. */ - memmove(p->fts_name, name, namelen); - p->fts_name[namelen] = '\0'; + 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); + } - if (!ISSET(FTS_NOSTAT)) - p->fts_statp = (struct STRUCT_STAT *)ALIGN(p->fts_name + namelen + 2); - p->fts_namelen = namelen; - p->fts_path = sp->fts_path; - p->fts_errno = 0; - p->fts_flags = 0; - p->fts_instr = FTS_NOINSTR; - p->fts_number = 0; - p->fts_pointer = NULL; - return (p); + /* 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 -fts_lfree (FTSENTRY *head) +fd_ring_print (FTSOBJ const *sp, FILE *stream, char const *msg) { - FTSENTRY *p; + 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; - /* Free a linked list of structures. */ - while ((p = head)) { - head = head->fts_link; - free(p); - } + 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 paths; find, rm, ls should all work on any tree. - * Most systems will allow creation of paths 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 path 2 bytes at a time. + * 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 int +static bool +internal_function fts_palloc (FTSOBJ *sp, size_t more) { - char *p; + size_t new_len = sp->fts_pathlen + more + 256; - sp->fts_pathlen += more + 256; - /* - * Check for possible wraparound. In an FTS, fts_pathlen is - * a signed int but in an FTSENT it is an unsigned short. - * We limit fts_pathlen to USHRT_MAX to be safe in both cases. - */ - if (sp->fts_pathlen < 0 || sp->fts_pathlen >= USHRT_MAX) { - free(sp->fts_path); - sp->fts_path = NULL; - __set_errno (ENAMETOOLONG); - return (1); - } - p = realloc(sp->fts_path, sp->fts_pathlen); - if (p == NULL) { - free(sp->fts_path); - sp->fts_path = NULL; - return 1; - } - sp->fts_path = p; - return 0; + /* + * 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 path is realloc'd, have to fix all of the pointers in structures - * already returned. + * 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) { - FTSENTRY *p; - char *addr = sp->fts_path; + char *addr = sp->fts_path; -#define ADJUST(p) do { \ - if ((p)->fts_accpath != (p)->fts_name) { \ - (p)->fts_accpath = \ - (char *)addr + ((p)->fts_accpath - (p)->fts_path); \ - } \ - (p)->fts_path = addr; \ + /* 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 (p = sp->fts_child; p; p = p->fts_link) - ADJUST(p); + /* 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 (p = head; p->fts_level >= FTS_ROOTLEVEL;) { - ADJUST(p); - p = p->fts_link ? p->fts_link : p->fts_parent; - } + /* 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 len, max; + size_t max; - for (max = 0; *argv; ++argv) - if ((len = strlen(*argv)) > max) - max = len; - return (max + 1); + 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 p->fts_accpath without getting + * Change to dir specified by fd or file name without getting * tricked by someone changing the world out from underneath us. - * Assumes p->fts_dev and p->fts_ino are filled in. + * 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 -fts_safe_changedir (FTSOBJ *sp, FTSENTRY *p, int fd, const char *path) +internal_function +fts_safe_changedir (FTSOBJ *sp, FTSENTRY *p, int fd, char const *dir) { - int ret, oerrno, newfd; - struct STRUCT_STAT sb; + fts_assert (0 <= fd || dir != NULL); + bool is_dotdot = dir && streq (dir, ".."); - newfd = fd; - if (ISSET(FTS_NOCHDIR)) - return (0); - if (fd < 0 && (newfd = __open(path, O_RDONLY, 0)) < 0) - return (-1); - if (FSTAT (newfd, &sb)) { - ret = -1; - goto bail; - } - if (p->fts_dev != sb.st_dev || p->fts_ino != sb.st_ino) { - __set_errno (ENOENT); /* disinformation */ - ret = -1; - goto bail; - } - ret = __fchdir(newfd); + /* 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: - oerrno = errno; - if (fd < 0) - (void)__close(newfd); - __set_errno (oerrno); - return (ret); + if (fd < 0) + { + int oerrno = errno; + (void)close(newfd); + __set_errno (oerrno); + } + return ret; } diff --git a/io/fts.h b/io/fts.h index b496e75cee..50636e6c87 100644 --- a/io/fts.h +++ b/io/fts.h @@ -52,7 +52,29 @@ #include #include +#include +#include +#include +enum { __I_RING_SIZE = 4 }; + +/* When ir_empty is true, the ring is empty. + Otherwise, ir_data[B..F] are defined, where B..F is the contiguous + range of indices, modulo I_RING_SIZE, from back to front, inclusive. + Undefined elements of ir_data are always set to ir_default_val. + Popping from an empty ring aborts. + Pushing onto a full ring returns the displaced value. + An empty ring has F==B and ir_empty == true. + A ring with one entry still has F==B, but now ir_empty == false. */ +struct __I_ring +{ + int ir_data[__I_RING_SIZE]; + int ir_default_val; + unsigned int ir_front; + unsigned int ir_back; + bool ir_empty; +}; +typedef struct __I_ring __I_ring; typedef struct { struct _ftsent *fts_cur; /* current node */ @@ -73,11 +95,106 @@ typedef struct { #define FTS_SEEDOT 0x0020 /* return dot and dot-dot */ #define FTS_XDEV 0x0040 /* don't cross devices */ #define FTS_WHITEOUT 0x0080 /* return whiteout information */ -#define FTS_OPTIONMASK 0x00ff /* valid user option mask */ + + /* There are two ways to detect cycles. + The lazy way (which works only with FTS_PHYSICAL), + with which one may process a directory that is a + part of the cycle several times before detecting the cycle. + The "tight" way, whereby fts uses more memory (proportional + to number of "active" directories, aka distance from root + of current tree to current directory -- see active_dir_ht) + to detect any cycle right away. For example, du must use + this option to avoid counting disk space in a cycle multiple + times, but chown -R need not. + The default is to use the constant-memory lazy way, when possible + (see below). + + However, with FTS_LOGICAL (when following symlinks, e.g., chown -L) + using lazy cycle detection is inadequate. For example, traversing + a directory containing a symbolic link to a peer directory, it is + possible to encounter the same directory twice even though there + is no cycle: + dir + ... + slink -> dir + So, when FTS_LOGICAL is selected, we have to use a different + mode of cycle detection: FTS_TIGHT_CYCLE_CHECK. */ +#define FTS_TIGHT_CYCLE_CHECK 0x0400 + + /* Use this flag to enable semantics with which the parent + application may be made both more efficient and more robust. + Whereas the default is to visit each directory in a recursive + traversal (via chdir), using this flag makes it so the initial + working directory is never changed. Instead, these functions + perform the traversal via a virtual working directory, maintained + through the file descriptor member, fts_cwd_fd. */ +# define FTS_CWDFD 0x0800 + + /* Historically, for each directory that fts initially encounters, it would + open it, read all entries, and stat each entry, storing the results, and + then it would process the first entry. But that behavior is bad for + locality of reference, and also causes trouble with inode-simulating + file systems like FAT, CIFS, FUSE-based ones, etc., when entries from + their name/inode cache are flushed too early. + Use this flag to make fts_open and fts_read defer the stat/lstat/fststat + of each entry until it is actually processed. However, note that if you + use this option and also specify a comparison function, that function may + not examine any data via fts_statp. However, when fts_statp->st_mode is + nonzero, the S_IFMT type bits are valid, with mapped dirent.d_type data. + Of course, that happens only on file systems that provide useful + dirent.d_type data. */ +#define FTS_DEFER_STAT 0x1000 + + /* Use this flag to disable stripping of trailing slashes + from input path names during fts_open initialization. */ +#define FTS_VERBATIM 0x2000 + +#define FTS_MOUNT 0x4000 /* skip other devices */ +#define FTS_OPTIONMASK 0x7fff /* valid user option mask */ #define FTS_NAMEONLY 0x0100 /* (private) child names only */ #define FTS_STOP 0x0200 /* (private) unrecoverable error */ + int fts_options; /* fts_open options, global flags */ + + int fts_cwd_fd; /* the file descriptor on which the + virtual cwd is open, or AT_FDCWD */ + + /* Map a directory's device number to a boolean. The boolean is + true if for that file system (type determined by a single fstatfs + call per FS) st_nlink can be used to calculate the number of + sub-directory entries in a directory. + Using this table is an optimization that permits us to look up + file system type on a per-inode basis at the minimal cost of + calling fstatfs only once per traversed device. */ + struct hash_table *fts_leaf_optimization_works_ht; + + union { + /* This data structure is used if FTS_TIGHT_CYCLE_CHECK is + specified. It records the directories between a starting + point and the current directory. I.e., a directory is + recorded here IFF we have visited it once, but we have not + yet completed processing of all its entries. Every time we + visit a new directory, we add that directory to this set. + When we finish with a directory (usually by visiting it a + second time), we remove it from this set. Each entry in + this data structure is a device/inode pair. This data + structure is used to detect directory cycles efficiently and + promptly even when the depth of a hierarchy is in the tens + of thousands. */ + struct hash_table *ht; + + /* FIXME: rename these two members to have the fts_ prefix */ + /* This data structure uses a lazy cycle-detection algorithm, + as done by rm via cycle-check.c. It's the default, + but it's not appropriate for programs like du. */ + struct cycle_check_state *state; + } fts_cycle; + + /* A stack of the file descriptors corresponding to the + most-recently traversed parent directories. + Currently used only in FTS_CWDFD mode. */ + __I_ring fts_fd_ring; } FTS; #ifdef __USE_LARGEFILE64 @@ -92,6 +209,13 @@ typedef struct { int fts_nitems; /* elements in the sort array */ int (*fts_compar) (const void *, const void *); /* compare fn */ int fts_options; /* fts_open options, global flags */ + int fts_cwd_fd; + struct hash_table *fts_leaf_optimization_works_ht; + union { + struct hash_table *ht; + struct cycle_check_state *state; + } fts_cycle; + __I_ring fts_fd_ring; } FTS64; #endif diff --git a/io/fts64-time64.c b/io/fts64-time64.c index f9bad60572..6a1053194e 100644 --- a/io/fts64-time64.c +++ b/io/fts64-time64.c @@ -19,18 +19,19 @@ #include #if __TIMESIZE != 64 -# define FTS_OPEN __fts64_open_time64 -# define FTS_CLOSE __fts64_close_time64 -# define FTS_READ __fts64_read_time64 -# define FTS_SET __fts64_set_time64 -# define FTS_CHILDREN __fts64_children_time64 -# define FTSOBJ FTS64_TIME64 -# define FTSENTRY FSTENT64_TIME64 -# define INO_T ino64_t -# define STRUCT_STAT __stat64_t64 -# define STAT __stat64_time64 -# define LSTAT __lstat64_time64 -# define FSTAT __fstat64_time64 +# define FTS_OPEN __fts64_open_time64 +# define FTS_CLOSE __fts64_close_time64 +# define FTS_READ __fts64_read_time64 +# define FTS_SET __fts64_set_time64 +# define FTS_CHILDREN __fts64_children_time64 +# define FTSOBJ FTS64_TIME64 +# define FTSENTRY FSTENT64_TIME64 +# define INO_T ino64_t +# define STRUCT_STAT __stat64_t64 +# define FSTAT __fstat64_time64 +# define FSTATAT __fstatat64_time64 +# define STRUCT_STATFS statfs64 +# define FSTATFS __fstatfs64 # include "fts.c" #endif diff --git a/io/fts64.c b/io/fts64.c index 152910018e..a6f607a873 100644 --- a/io/fts64.c +++ b/io/fts64.c @@ -25,8 +25,9 @@ #define FTSENTRY FTSENT64 #define INO_T ino64_t #define STRUCT_STAT stat64 -#define STAT __stat64 -#define LSTAT __lstat64 #define FSTAT __fstat64 +#define FSTATAT __fstatat64 +#define STRUCT_STATFS statfs64 +#define FSTATFS __fstatfs64 #include "fts.c" diff --git a/io/i-ring.c b/io/i-ring.c new file mode 100644 index 0000000000..b4e47904dd --- /dev/null +++ b/io/i-ring.c @@ -0,0 +1,72 @@ +/* a simple ring buffer + Copyright (C) 2006, 2009-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 . */ + +/* written by Jim Meyering */ + +#include +#if !_LIBC +# include "i-ring.h" +# define INTERNAL_DEF +#else +# define I_ring __I_ring +# define I_RING_SIZE __I_RING_SIZE +#endif + +#include + +INTERNAL_DEF void +i_ring_init (I_ring *ir, int default_val) +{ + ir->ir_empty = true; + ir->ir_front = 0; + ir->ir_back = 0; + for (int i = 0; i < I_RING_SIZE; i++) + ir->ir_data[i] = default_val; + ir->ir_default_val = default_val; +} + +INTERNAL_DEF bool +i_ring_empty (I_ring const *ir) +{ + return ir->ir_empty; +} + +INTERNAL_DEF int +i_ring_push (I_ring *ir, int val) +{ + unsigned int dest_idx = (ir->ir_front + !ir->ir_empty) % I_RING_SIZE; + int old_val = ir->ir_data[dest_idx]; + ir->ir_data[dest_idx] = val; + ir->ir_front = dest_idx; + if (dest_idx == ir->ir_back) + ir->ir_back = (ir->ir_back + !ir->ir_empty) % I_RING_SIZE; + ir->ir_empty = false; + return old_val; +} + +INTERNAL_DEF int +i_ring_pop (I_ring *ir) +{ + if (i_ring_empty (ir)) + abort (); + int top_val = ir->ir_data[ir->ir_front]; + ir->ir_data[ir->ir_front] = ir->ir_default_val; + if (ir->ir_front == ir->ir_back) + ir->ir_empty = true; + else + ir->ir_front = ((ir->ir_front + I_RING_SIZE - 1) % I_RING_SIZE); + return top_val; +} diff --git a/io/same-inode.h b/io/same-inode.h new file mode 100644 index 0000000000..44b6f3ed84 --- /dev/null +++ b/io/same-inode.h @@ -0,0 +1,106 @@ +/* Determine whether two stat buffers are known to refer to the same file. + + Copyright (C) 2006, 2009-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 . */ + +#ifndef SAME_INODE_H +#define SAME_INODE_H 1 + +/* This file uses _GL_INLINE_HEADER_BEGIN, _GL_INLINE. */ +#if !_LIBC && !_GL_CONFIG_H_INCLUDED + #error "Please include config.h first." +#endif + +#include +#if _LIBC +# define _GL_INLINE_HEADER_BEGIN +# define _GL_INLINE_HEADER_END +# define SAME_INODE_INLINE static inline +# define S_TYPEISTMO(p) 0 +#endif + +_GL_INLINE_HEADER_BEGIN +#ifndef SAME_INODE_INLINE +# define SAME_INODE_INLINE _GL_INLINE +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + +/* True if A and B point to structs with st_dev and st_ino members + that are known to represent the same file. + + Use | and ^ to shorten generated code, and to lessen the + probability of screwups if st_ino is an array. */ + +#if defined __VMS && __CRTL_VER < 80200000 +# define PSAME_INODE(a, b) (! (((a)->st_dev ^ (b)->st_dev) \ + | ((a)->st_ino[0] ^ (b)->st_ino[0]) \ + | ((a)->st_ino[1] ^ (b)->st_ino[1]) \ + | ((a)->st_ino[2] ^ (b)->st_ino[2]))) +#elif defined _WIN32 && ! defined __CYGWIN__ + /* Native Windows. */ +# if _GL_WINDOWS_STAT_INODES + /* stat() and fstat() set st_dev and st_ino to 0 if information about + the inode is not available. */ +# if _GL_WINDOWS_STAT_INODES == 2 +# define PSAME_INODE(a, b) \ + (! (! ((a)->st_dev | (a)->st_ino._gl_ino[0] | (a)->st_ino._gl_ino[1]) \ + | ((a)->st_dev ^ (b)->st_dev) \ + | ((a)->st_ino._gl_ino[0] ^ (b)->st_ino._gl_ino[0]) \ + | ((a)->st_ino._gl_ino[1] ^ (b)->st_ino._gl_ino[1]))) +# else +# define PSAME_INODE(a, b) (! (! ((a)->st_dev | (a)->st_ino) \ + | ((a)->st_dev ^ (b)->st_dev) \ + | ((a)->st_ino ^ (b)->st_ino))) +# endif +# else + /* stat() and fstat() set st_ino to 0 always. */ +# define PSAME_INODE(a, b) 0 +# endif +#else + /* POSIX. */ +# define PSAME_INODE(a, b) (! (((a)->st_dev ^ (b)->st_dev) \ + | ((a)->st_ino ^ (b)->st_ino))) +#endif + +/* True if struct objects A and B are known to represent the same file. */ + +#define SAME_INODE(a, b) PSAME_INODE (&(a), &(b)) + +/* True if *A and *B represent the same file. Unlike PSAME_INODE, + args are evaluated once and must point to struct stat, + and this function works even on POSIX platforms where fstat etc. do + not return useful st_dev and st_ino values for shared memory + objects and typed memory objects. */ + +SAME_INODE_INLINE bool +psame_inode (struct stat const *a, struct stat const *b) +{ + return (! (S_TYPEISSHM (a) | S_TYPEISTMO (a) + | S_TYPEISSHM (b) | S_TYPEISTMO (b)) + && PSAME_INODE (a, b)); +} + + +#ifdef __cplusplus +} +#endif + +_GL_INLINE_HEADER_END + +#endif diff --git a/io/tst-fts-bz22944.c b/io/tst-fts-bz22944.c new file mode 100644 index 0000000000..7f86045234 --- /dev/null +++ b/io/tst-fts-bz22944.c @@ -0,0 +1,100 @@ +/* Check that fts does not fail with very long paths. + Copyright (C) 2026 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include +#include +#include + +#define BASENAME "tst-fts-bz22944." + +enum { nested_depth = 150 }; +static const char dir_name[] = { [0 ... 254] = 'A', '\0' }; + +static void +do_cleanup (void) +{ + xchdir (".."); + for (int i = 0; i < nested_depth; i++) + { + remove (dir_name); + xchdir (".."); + } + remove (dir_name); +} +#define CLEANUP_HANDLER do_cleanup + +static void +check_mkdir (const char *path) +{ + int r = mkdir (path, 0700); + /* Some filesystem such as overlayfs does not support larger path required + to trigger the internal buffer reallocation. */ + if (r != 0) + { + if (errno == ENAMETOOLONG) + FAIL_UNSUPPORTED ("the filesystem does not support the required" + "large path"); + else + FAIL_EXIT1 ("mkdir (\"%s\", 0%o): %m", path, 0700); + } +} + +int +do_test (void) +{ + char *tempdir = support_create_temp_directory (BASENAME); + xchdir (tempdir); + for (int i = 0; i < nested_depth; i++) + { + check_mkdir (dir_name); + xchdir (dir_name); + } + + char *paths[] = { tempdir , 0 }; + FTS *ftsp = fts_open (paths, FTS_XDEV | FTS_COMFOLLOW | FTS_PHYSICAL, 0); + TEST_VERIFY_EXIT (ftsp != NULL); + + int num_dirs = 0; + + FTSENT *ent; + while ((ent = fts_read(ftsp)) != 0) + { + switch (ent->fts_info) + { + case FTS_D: + num_dirs++; + break; + default: + break; + } + } + + TEST_COMPARE (num_dirs, nested_depth + 1); + TEST_COMPARE (errno, 0); + + fts_close (ftsp); + + do_cleanup (); + + return 0; +} + +#include diff --git a/io/tst-fts-newflags.c b/io/tst-fts-newflags.c new file mode 100644 index 0000000000..a40561268b --- /dev/null +++ b/io/tst-fts-newflags.c @@ -0,0 +1,234 @@ +/* Simple tests for some gnulib imported fts features. + Copyright (C) 2026 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static char *tempdir; + +static void +do_prepare (int argc, char **argv) +{ + tempdir = support_create_temp_directory ("tst-fts-newflags"); + + /* Create directory tree: + tempdir/dir + tempdir/dir/file + tempdir/dir/symlink -> tempdir (cycle) */ + char *path; + + path = xasprintf ("%s/dir", tempdir); + xmkdir (path, 0777); + add_temp_file (path); + free (path); + + path = xasprintf ("%s/dir/file", tempdir); + int fd = xopen (path, O_CREAT | O_RDWR, 0666); + xclose (fd); + add_temp_file (path); + free (path); + + path = xasprintf ("%s/dir/symlink", tempdir); + xsymlink (tempdir, path); + add_temp_file (path); + free (path); +} +#define PREPARE do_prepare + +/* FTS_TIGHT_CYCLE_CHECK: we use FTS_LOGICAL to follow the symlink we + created, causing a loop. The tight cycle checker should catch this + immediately and emit FTS_DC. */ +static void +test_tight_cycle (void) +{ + char *paths[] = { tempdir, NULL }; + FTS *fts = fts_open (paths, FTS_LOGICAL | FTS_TIGHT_CYCLE_CHECK, NULL); + TEST_VERIFY_EXIT (fts != NULL); + + FTSENT *ent; + bool found_cycle = false; + while ((ent = fts_read (fts)) != NULL) + { + if (ent->fts_info == FTS_DC) + { + found_cycle = true; + break; + } + } + TEST_VERIFY (found_cycle); + fts_close (fts); +} + +/* FTS_CWDFD: ensures that fts uses virtual file descriptors and does not + actually change the process's global working directory. */ +static void +test_cwdfd (void) +{ + char *paths[] = { tempdir, NULL }; + FTS *fts = fts_open (paths, FTS_PHYSICAL | FTS_CWDFD, NULL); + TEST_VERIFY_EXIT (fts != NULL); + + char *cwd_before = getcwd (NULL, 0); + TEST_VERIFY_EXIT (cwd_before != NULL); + + FTSENT *ent; + while ((ent = fts_read (fts)) != NULL) + { + } + + char *cwd_after = getcwd (NULL, 0); + TEST_VERIFY_EXIT (cwd_after != NULL); + + /* If FTS_CWDFD works, the global CWD remains untouched. */ + TEST_COMPARE_STRING (cwd_before, cwd_after); + + free (cwd_before); + free (cwd_after); + fts_close (fts); +} + +/* FTS_DEFER_STAT: verifies that deferring the stat calls does not break + standard physical tree traversals. */ +static void +test_defer_stat (void) +{ + char *paths[] = { tempdir, NULL }; + FTS *fts = fts_open (paths, FTS_PHYSICAL | FTS_DEFER_STAT, NULL); + TEST_VERIFY_EXIT (fts != NULL); + + FTSENT *ent; + int count = 0; + while ((ent = fts_read (fts)) != NULL) + { + count++; + } + + /* Expects: + + 1: $tmpdir + 2: $tmpdir/dir + 3: $tmpdir/dir/file + 4: $tmpdir/dir/symlink + 5: $tmpdir/dir/ + 6: $tmpdir/ */ + TEST_COMPARE (count, 6); + fts_close (fts); +} + +/* FTS_MOUNT: validates that the traversal refuses to cross into a different + file system when the flag is set. Assumes /proc is on a different mount. */ +static void +test_mount (void) +{ + /* Create a symlink to /proc, which resides on a different mount point. */ + char *link_path = xasprintf ("%s/mnt_link", tempdir); + xsymlink ("/proc", link_path); + + /* Traverse with FTS_LOGICAL so fts resolves the symlink and sees the + /proc device ID, but add FTS_MOUNT to enforce the boundary. */ + char *paths[] = { tempdir, NULL }; + FTS *fts = fts_open (paths, FTS_LOGICAL | FTS_MOUNT, NULL); + TEST_VERIFY_EXIT (fts != NULL); + + FTSENT *ent; + bool found_mnt_link = false; + while ((ent = fts_read (fts)) != NULL) + { + if (strcmp (ent->fts_name, "mnt_link") == 0) + found_mnt_link = true; + } + + /* Because the device ID of /proc differs from the root traversal + device ID, FTS_MOUNT forces fts to skip the entry entirely. */ + TEST_VERIFY (!found_mnt_link); + + fts_close (fts); + unlink (link_path); + free (link_path); +} + +/* FTS_VERBATIM: Ensures paths are passed through without having trailing + slashes stripped. */ +static void +test_verbatim (void) +{ + char *path_with_slashes = xasprintf ("%s///", tempdir); + char *paths[] = { path_with_slashes, NULL }; + + FTS *fts = fts_open (paths, FTS_PHYSICAL | FTS_VERBATIM, NULL); + TEST_VERIFY_EXIT (fts != NULL); + + FTSENT *ent = fts_read (fts); + TEST_VERIFY_EXIT (ent != NULL); + + /* The returned root path should retain the exact slashes passed to it. */ + TEST_COMPARE_STRING (ent->fts_path, path_with_slashes); + + fts_close (fts); + free (path_with_slashes); +} + +/* Main test driver */ +static int +do_test (void) +{ + support_need_proc ("FTS_MOUNT test requires /proc"); + + { + /* Check if fts_open does not fail if neither FTS_LOGICAL nor FTS_PHYSICAL + are specified. */ + char *paths[] = { tempdir, NULL }; + FTS *fts = fts_open (paths, 0, NULL); + TEST_VERIFY_EXIT (fts != NULL); + fts_close (fts); + } + + { + FTS *fts; + char *paths[] = { tempdir, NULL }; + + /* There are internal only flags. */ + fts = fts_open (paths, FTS_NAMEONLY, NULL); + TEST_VERIFY_EXIT (fts == NULL); + TEST_COMPARE (errno, EINVAL); + + fts = fts_open (paths, FTS_STOP, NULL); + TEST_VERIFY_EXIT (fts == NULL); + TEST_COMPARE (errno, EINVAL); + } + + test_tight_cycle (); + test_cwdfd (); + test_defer_stat (); + test_mount (); + test_verbatim (); + + return 0; +} + +/* Includes the glibc test framework driver */ +#include diff --git a/io/tst-fts.c b/io/tst-fts.c index 494eb29c9a..ca82377d20 100644 --- a/io/tst-fts.c +++ b/io/tst-fts.c @@ -192,6 +192,8 @@ do_test (void) don't use FTS_LOGICAL. */ #ifndef TST_FTS_Y2038 flags |= FTS_LOGICAL; +#else + flags |= FTS_PHYSICAL; #endif fts = fts_open (paths, flags, &compare_ents); if (fts == NULL) diff --git a/misc/Makefile b/misc/Makefile index fc44de9934..4395366d74 100644 --- a/misc/Makefile +++ b/misc/Makefile @@ -121,6 +121,7 @@ routines := \ getusershell \ getxattr \ gtty \ + hash \ hsearch \ hsearch_r \ ifunc-impl-list \ @@ -157,6 +158,7 @@ routines := \ munlock \ munlockall \ munmap \ + next-prime \ preadv \ preadv2 \ preadv64 \ diff --git a/misc/hash.c b/misc/hash.c new file mode 100644 index 0000000000..f722713a8f --- /dev/null +++ b/misc/hash.c @@ -0,0 +1,1045 @@ +/* hash - hashing table processing. + + Copyright (C) 1998-2004, 2006-2007, 2009-2026 Free Software Foundation, Inc. + + Written by Jim Meyering, 1992. + + 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 . */ + +/* A generic hash table package. */ + +/* Define USE_OBSTACK to 1 if you want the allocator to use obstacks instead + of malloc. If you change USE_OBSTACK, you have to recompile! */ + +#include +#ifdef _LIBC +# define USE_OBSTACK 0 +# define TESTING 0 +# include +# define rotr_sz(__x, __n) stdc_rotate_right(__x, __n) +#else +# include "bitrotate.h" +#endif + +#include "hash.h" + +#include "next-prime.h" +#include "xalloc-oversized.h" + +#include +#include +#include +#include + +#if USE_OBSTACK +# include "obstack.h" +# ifndef obstack_chunk_alloc +# define obstack_chunk_alloc malloc +# endif +# ifndef obstack_chunk_free +# define obstack_chunk_free free +# endif +#endif + +struct hash_entry + { + void *data; + struct hash_entry *next; + /* Invariant: If DATA is NULL, this entry is an unused bucket head, + and therefore NEXT is NULL as well. */ + }; + +struct hash_table + { + /* The array of buckets starts at BUCKET and extends to BUCKET_LIMIT-1, + for a possibility of N_BUCKETS. Among those, N_BUCKETS_USED buckets + are not empty, there are N_ENTRIES active entries in the table. */ + struct hash_entry *bucket; + struct hash_entry const *bucket_limit; + size_t n_buckets; + size_t n_buckets_used; + size_t n_entries; + + /* Tuning arguments, kept in a physically separate structure. */ + const Hash_tuning *tuning; + + /* Three functions are given to 'hash_initialize', see the documentation + block for this function. In a word, HASHER randomizes a user entry + into a number up from 0 up to some maximum minus 1; COMPARATOR returns + true if two user entries compare equally; and DATA_FREER is the cleanup + function for a user entry. */ + Hash_hasher hasher; + Hash_comparator comparator; + Hash_data_freer data_freer; + + /* A linked list of freed struct hash_entry structs. */ + struct hash_entry *free_entry_list; + +#if USE_OBSTACK + /* Whenever obstacks are used, it is possible to allocate all overflowed + entries into a single stack, so they all can be freed in a single + operation. It is not clear if the speedup is worth the trouble. */ + struct obstack entry_stack; +#endif + }; + +/* A hash table contains many internal entries, each holding a pointer to + some user-provided data (also called a user entry). An entry indistinctly + refers to both the internal entry and its associated user entry. A user + entry contents may be hashed by a randomization function (the hashing + function, or just "hasher" for short) into a number (or "slot") between 0 + and the current table size. At each slot position in the hash table, + starts a linked chain of entries for which the user data all hash to this + slot. A bucket is the collection of all entries hashing to the same slot. + + A good "hasher" function will distribute entries rather evenly in buckets. + In the ideal case, the length of each bucket is roughly the number of + entries divided by the table size. Finding the slot for a data is usually + done in constant time by the "hasher", and the later finding of a precise + entry is linear in time with the size of the bucket. Consequently, a + larger hash table size (that is, a larger number of buckets) is prone to + yielding shorter chains, *given* the "hasher" function behaves properly. + + Long buckets slow down the lookup algorithm. One might use big hash table + sizes in hope to reduce the average length of buckets, but this might + become inordinate, as unused slots in the hash table take some space. The + best bet is to make sure you are using a good "hasher" function (beware + that those are not that easy to write! :-), and to use a table size + larger than the actual number of entries. */ + +/* If an insertion makes the ratio of nonempty buckets to table size larger + than the growth threshold (a number between 0.0 and 1.0), then increase + the table size by multiplying by the growth factor (a number greater than + 1.0). The growth threshold defaults to 0.8, and the growth factor + defaults to 1.414, meaning that the table will have doubled its size + every second time 80% of the buckets get used. */ +#define DEFAULT_GROWTH_THRESHOLD 0.8f +#define DEFAULT_GROWTH_FACTOR 1.414f + +/* If a deletion empties a bucket and causes the ratio of used buckets to + table size to become smaller than the shrink threshold (a number between + 0.0 and 1.0), then shrink the table by multiplying by the shrink factor (a + number greater than the shrink threshold but smaller than 1.0). The shrink + threshold and factor default to 0.0 and 1.0, meaning that the table never + shrinks. */ +#define DEFAULT_SHRINK_THRESHOLD 0.0f +#define DEFAULT_SHRINK_FACTOR 1.0f + +/* Use this to initialize or reset a TUNING structure to + some sensible values. */ +static const Hash_tuning default_tuning = + { + DEFAULT_SHRINK_THRESHOLD, + DEFAULT_SHRINK_FACTOR, + DEFAULT_GROWTH_THRESHOLD, + DEFAULT_GROWTH_FACTOR, + false + }; + +/* Information and lookup. */ + +#if !_LIBC +size_t +hash_get_n_buckets (const Hash_table *table) +{ + return table->n_buckets; +} + +size_t +hash_get_n_buckets_used (const Hash_table *table) +{ + return table->n_buckets_used; +} + +size_t +hash_get_n_entries (const Hash_table *table) +{ + return table->n_entries; +} + +size_t +hash_get_max_bucket_length (const Hash_table *table) +{ + size_t max_bucket_length = 0; + + for (struct hash_entry const *bucket = table->bucket; + bucket < table->bucket_limit; + bucket++) + { + if (bucket->data) + { + struct hash_entry const *cursor = bucket; + size_t bucket_length = 1; + + while (cursor = cursor->next, cursor) + bucket_length++; + + if (bucket_length > max_bucket_length) + max_bucket_length = bucket_length; + } + } + + return max_bucket_length; +} + +bool +hash_table_ok (const Hash_table *table) +{ + size_t n_buckets_used = 0; + size_t n_entries = 0; + + for (struct hash_entry const *bucket = table->bucket; + bucket < table->bucket_limit; + bucket++) + { + if (bucket->data) + { + struct hash_entry const *cursor = bucket; + + /* Count bucket head. */ + n_buckets_used++; + n_entries++; + + /* Count bucket overflow. */ + while (cursor = cursor->next, cursor) + n_entries++; + } + } + + if (n_buckets_used == table->n_buckets_used && n_entries == table->n_entries) + return true; + + return false; +} + +void +hash_print_statistics (const Hash_table *table, FILE *stream) +{ + size_t n_entries = hash_get_n_entries (table); + size_t n_buckets = hash_get_n_buckets (table); + size_t n_buckets_used = hash_get_n_buckets_used (table); + size_t max_bucket_length = hash_get_max_bucket_length (table); + + fprintf (stream, "# entries: %lu\n", (unsigned long int) n_entries); + fprintf (stream, "# buckets: %lu\n", (unsigned long int) n_buckets); + fprintf (stream, "# buckets used: %lu (%.2f%%)\n", + (unsigned long int) n_buckets_used, + (100.0 * n_buckets_used) / n_buckets); + fprintf (stream, "max bucket length: %lu\n", + (unsigned long int) max_bucket_length); +} +#endif + +/* Hash KEY and return a pointer to the selected bucket. + If TABLE->hasher misbehaves, abort. */ +static struct hash_entry * +safe_hasher (const Hash_table *table, const void *key) +{ + size_t n = table->hasher (key, table->n_buckets); + if (! (n < table->n_buckets)) + abort (); + return table->bucket + n; +} + +void * +hash_lookup (const Hash_table *table, const void *entry) +{ + struct hash_entry const *bucket = safe_hasher (table, entry); + + if (bucket->data == NULL) + return NULL; + + for (struct hash_entry const *cursor = bucket; cursor; cursor = cursor->next) + if (entry == cursor->data || table->comparator (entry, cursor->data)) + return cursor->data; + + return NULL; +} + +#if !_LIBC +/* Walking. */ + +void * +hash_get_first (const Hash_table *table) +{ + if (table->n_entries == 0) + return NULL; + + for (struct hash_entry const *bucket = table->bucket; ; bucket++) + if (! (bucket < table->bucket_limit)) + abort (); + else if (bucket->data) + return bucket->data; +} + +void * +hash_get_next (const Hash_table *table, const void *entry) +{ + struct hash_entry const *bucket = safe_hasher (table, entry); + + /* Find next entry in the same bucket. */ + { + struct hash_entry const *cursor = bucket; + do + { + if (cursor->data == entry && cursor->next) + return cursor->next->data; + cursor = cursor->next; + } + while (cursor != NULL); + } + + /* Find first entry in any subsequent bucket. */ + while (++bucket < table->bucket_limit) + if (bucket->data) + return bucket->data; + + /* None found. */ + return NULL; +} + +size_t +hash_get_entries (const Hash_table *table, void **buffer, + size_t buffer_size) +{ + size_t counter = 0; + + for (struct hash_entry const *bucket = table->bucket; + bucket < table->bucket_limit; + bucket++) + { + if (bucket->data) + { + for (struct hash_entry const *cursor = bucket; cursor; cursor = cursor->next) + { + if (counter >= buffer_size) + return counter; + buffer[counter++] = cursor->data; + } + } + } + + return counter; +} + +size_t +hash_do_for_each (const Hash_table *table, Hash_processor processor, + void *processor_data) +{ + size_t counter = 0; + + for (struct hash_entry const *bucket = table->bucket; + bucket < table->bucket_limit; + bucket++) + { + if (bucket->data) + { + for (struct hash_entry const *cursor = bucket; cursor; cursor = cursor->next) + { + if (! processor (cursor->data, processor_data)) + return counter; + counter++; + } + } + } + + return counter; +} +#endif + + +/* If the user passes a NULL hasher, we hash the raw pointer. */ +static size_t +raw_hasher (const void *data, size_t n) +{ + /* When hashing unique pointers, it is often the case that they were + generated by malloc and thus have the property that the low-order + bits are 0. As this tends to give poorer performance with small + tables, we rotate the pointer value before performing division, + in an attempt to improve hash quality. */ + size_t val = rotr_sz ((size_t) data, 3); + return val % n; +} + +/* If the user passes a NULL comparator, we use pointer comparison. */ +static bool +raw_comparator (const void *a, const void *b) +{ + return a == b; +} + + +#if !_LIBC +/* Allocation and clean-up. */ + +void +hash_reset_tuning (Hash_tuning *tuning) +{ + *tuning = default_tuning; +} +#endif + +/* For the given hash TABLE, check the user supplied tuning structure for + reasonable values, and return true if there is no gross error with it. + Otherwise, definitively reset the TUNING field to some acceptable default + in the hash table (that is, the user loses the right of further modifying + tuning arguments), and return false. */ + +static bool +check_tuning (Hash_table *table) +{ + const Hash_tuning *tuning = table->tuning; + if (tuning == &default_tuning) + return true; + + /* Be a bit stricter than mathematics would require, so that + rounding errors in size calculations do not cause allocations to + fail to grow or shrink as they should. The smallest allocation + is 11 (due to next_prime's algorithm), so an epsilon of 0.1 + should be good enough. */ + float epsilon = 0.1f; + + if (epsilon < tuning->growth_threshold + && tuning->growth_threshold < 1 - epsilon + && 1 + epsilon < tuning->growth_factor + && 0 <= tuning->shrink_threshold + && tuning->shrink_threshold + epsilon < tuning->shrink_factor + && tuning->shrink_factor <= 1 + && tuning->shrink_threshold + epsilon < tuning->growth_threshold) + return true; + + table->tuning = &default_tuning; + return false; +} + +/* Compute the size of the bucket array for the given CANDIDATE and + TUNING, or return 0 if there is no possible way to allocate that + many entries. */ + +static size_t _GL_ATTRIBUTE_PURE +compute_bucket_size (size_t candidate, const Hash_tuning *tuning) +{ + if (!tuning->is_n_buckets) + { + float new_candidate = candidate / tuning->growth_threshold; + if ((float) SIZE_MAX <= new_candidate) + goto nomem; + candidate = new_candidate; + } + candidate = next_prime (candidate); + if (xalloc_oversized (candidate, sizeof (struct hash_entry *))) + goto nomem; + return candidate; + + nomem: + errno = ENOMEM; + return 0; +} + +Hash_table * +hash_initialize (size_t candidate, const Hash_tuning *tuning, + Hash_hasher hasher, Hash_comparator comparator, + Hash_data_freer data_freer) +{ + if (hasher == NULL) + hasher = raw_hasher; + if (comparator == NULL) + comparator = raw_comparator; + + Hash_table *table = malloc (sizeof *table); + if (table == NULL) + return NULL; + + if (!tuning) + tuning = &default_tuning; + table->tuning = tuning; + if (!check_tuning (table)) + { + /* Fail if the tuning options are invalid. This is the only occasion + when the user gets some feedback about it. Once the table is created, + if the user provides invalid tuning options, we silently revert to + using the defaults, and ignore further request to change the tuning + options. */ + errno = EINVAL; + goto fail; + } + + table->n_buckets = compute_bucket_size (candidate, tuning); + if (!table->n_buckets) + goto fail; + + table->bucket = calloc (table->n_buckets, sizeof *table->bucket); + if (table->bucket == NULL) + goto fail; + table->bucket_limit = table->bucket + table->n_buckets; + table->n_buckets_used = 0; + table->n_entries = 0; + + table->hasher = hasher; + table->comparator = comparator; + table->data_freer = data_freer; + + table->free_entry_list = NULL; +#if USE_OBSTACK + obstack_init (&table->entry_stack); +#endif + return table; + + fail: + free (table); + return NULL; +} + +#if !_LIBC +void +hash_clear (Hash_table *table) +{ + for (struct hash_entry *bucket = table->bucket; + bucket < table->bucket_limit; + bucket++) + { + if (bucket->data) + { + /* Free the bucket overflow. */ + for (struct hash_entry *cursor = bucket->next; cursor; ) + { + if (table->data_freer) + table->data_freer (cursor->data); + cursor->data = NULL; + + struct hash_entry *next = cursor->next; + /* Relinking is done one entry at a time, as it is to be expected + that overflows are either rare or short. */ + cursor->next = table->free_entry_list; + table->free_entry_list = cursor; + + cursor = next; + } + + /* Free the bucket head. */ + if (table->data_freer) + table->data_freer (bucket->data); + bucket->data = NULL; + bucket->next = NULL; + } + } + + table->n_buckets_used = 0; + table->n_entries = 0; +} +#endif + +void +hash_free (Hash_table *table) +{ + int saved_errno = errno; + + /* Call the user data_freer function. */ + if (table->data_freer && table->n_entries) + { + for (struct hash_entry *bucket = table->bucket; + bucket < table->bucket_limit; + bucket++) + { + if (bucket->data) + { + for (struct hash_entry *cursor = bucket; cursor; cursor = cursor->next) + table->data_freer (cursor->data); + } + } + } + +#if USE_OBSTACK + + obstack_free (&table->entry_stack, NULL); + +#else + + /* Free all bucket overflowed entries. */ + for (struct hash_entry *bucket = table->bucket; + bucket < table->bucket_limit; + bucket++) + { + for (struct hash_entry *cursor = bucket->next; cursor; ) + { + struct hash_entry *next = cursor->next; + free (cursor); + cursor = next; + } + } + + /* Also reclaim the internal list of previously freed entries. */ + for (struct hash_entry *cursor = table->free_entry_list; cursor; ) + { + struct hash_entry *next = cursor->next; + free (cursor); + cursor = next; + } + +#endif + + /* Free the remainder of the hash table structure. */ + free (table->bucket); + free (table); + + errno = saved_errno; +} + +/* Insertion and deletion. */ + +/* Get a new hash entry for a bucket overflow, possibly by recycling a + previously freed one. If this is not possible, allocate a new one. */ + +static struct hash_entry * +allocate_entry (Hash_table *table) +{ + struct hash_entry *new; + + if (table->free_entry_list) + { + new = table->free_entry_list; + table->free_entry_list = new->next; + } + else + { +#if USE_OBSTACK + new = obstack_alloc (&table->entry_stack, sizeof *new); +#else + new = malloc (sizeof *new); +#endif + } + + return new; +} + +/* Free a hash entry which was part of some bucket overflow, + saving it for later recycling. */ + +static void +free_entry (Hash_table *table, struct hash_entry *entry) +{ + entry->data = NULL; + entry->next = table->free_entry_list; + table->free_entry_list = entry; +} + +/* This private function is used to help with insertion and deletion. When + ENTRY matches an entry in the table, return a pointer to the corresponding + user data and set *BUCKET_HEAD to the head of the selected bucket. + Otherwise, return NULL. When DELETE is true and ENTRY matches an entry in + the table, unlink the matching entry. */ + +static void * +find_entry (Hash_table *table, const void *entry, + struct hash_entry **bucket_head, bool delete) +{ + struct hash_entry *bucket = safe_hasher (table, entry); + + *bucket_head = bucket; + + /* Test for empty bucket. */ + if (bucket->data == NULL) + return NULL; + + /* See if the entry is the first in the bucket. */ + if (entry == bucket->data || table->comparator (entry, bucket->data)) + { + void *data = bucket->data; + + if (delete) + { + if (bucket->next) + { + struct hash_entry *next = bucket->next; + + /* Bump the first overflow entry into the bucket head, then save + the previous first overflow entry for later recycling. */ + *bucket = *next; + free_entry (table, next); + } + else + { + bucket->data = NULL; + } + } + + return data; + } + + /* Scan the bucket overflow. */ + for (struct hash_entry *cursor = bucket; cursor->next; cursor = cursor->next) + { + if (entry == cursor->next->data + || table->comparator (entry, cursor->next->data)) + { + void *data = cursor->next->data; + + if (delete) + { + struct hash_entry *next = cursor->next; + + /* Unlink the entry to delete, then save the freed entry for later + recycling. */ + cursor->next = next->next; + free_entry (table, next); + } + + return data; + } + } + + /* No entry found. */ + return NULL; +} + +/* Internal helper, to move entries from SRC to DST. Both tables must + share the same free entry list. If SAFE, only move overflow + entries, saving bucket heads for later, so that no allocations will + occur. Return false (setting errno) if the free entry list is + exhausted and an allocation fails. */ + +static bool +transfer_entries (Hash_table *dst, Hash_table *src, bool safe) +{ + for (struct hash_entry *bucket = src->bucket; bucket < src->bucket_limit; bucket++) + if (bucket->data) + { + void *data; + struct hash_entry *new_bucket; + + /* Within each bucket, transfer overflow entries first and + then the bucket head, to minimize memory pressure. After + all, the only time we might allocate is when moving the + bucket head, but moving overflow entries first may create + free entries that can be recycled by the time we finally + get to the bucket head. */ + for (struct hash_entry *cursor = bucket->next; cursor; ) + { + data = cursor->data; + new_bucket = safe_hasher (dst, data); + + struct hash_entry *next = cursor->next; + + if (new_bucket->data) + { + /* Merely relink an existing entry, when moving from a + bucket overflow into a bucket overflow. */ + cursor->next = new_bucket->next; + new_bucket->next = cursor; + } + else + { + /* Free an existing entry, when moving from a bucket + overflow into a bucket header. */ + new_bucket->data = data; + dst->n_buckets_used++; + free_entry (dst, cursor); + } + + cursor = next; + } + /* Now move the bucket head. Be sure that if we fail due to + allocation failure that the src table is in a consistent + state. */ + data = bucket->data; + bucket->next = NULL; + if (!safe) + { + new_bucket = safe_hasher (dst, data); + + if (new_bucket->data) + { + /* Allocate or recycle an entry, when moving from a bucket + header into a bucket overflow. */ + struct hash_entry *new_entry = allocate_entry (dst); + + if (new_entry == NULL) + return false; + + new_entry->data = data; + new_entry->next = new_bucket->next; + new_bucket->next = new_entry; + } + else + { + /* Move from one bucket header to another. */ + new_bucket->data = data; + dst->n_buckets_used++; + } + bucket->data = NULL; + src->n_buckets_used--; + } + } + return true; +} + +bool +hash_rehash (Hash_table *table, size_t candidate) +{ + size_t new_size = compute_bucket_size (candidate, table->tuning); + + if (!new_size) + return false; + if (new_size == table->n_buckets) + return true; + + Hash_table storage; + Hash_table *new_table = &storage; + new_table->bucket = calloc (new_size, sizeof *new_table->bucket); + if (new_table->bucket == NULL) + return false; + new_table->n_buckets = new_size; + new_table->bucket_limit = new_table->bucket + new_size; + new_table->n_buckets_used = 0; + new_table->n_entries = 0; + new_table->tuning = table->tuning; + new_table->hasher = table->hasher; + new_table->comparator = table->comparator; + new_table->data_freer = table->data_freer; + + /* In order for the transfer to successfully complete, we need + additional overflow entries when distinct buckets in the old + table collide into a common bucket in the new table. The worst + case possible is a hasher that gives a good spread with the old + size, but returns a constant with the new size; if we were to + guarantee table->n_buckets_used-1 free entries in advance, then + the transfer would be guaranteed to not allocate memory. + However, for large tables, a guarantee of no further allocation + introduces a lot of extra memory pressure, all for an unlikely + corner case (most rehashes reduce, rather than increase, the + number of overflow entries needed). So, we instead ensure that + the transfer process can be reversed if we hit a memory + allocation failure mid-transfer. */ + + /* Merely reuse the extra old space into the new table. */ +#if USE_OBSTACK + new_table->entry_stack = table->entry_stack; +#endif + new_table->free_entry_list = table->free_entry_list; + + if (transfer_entries (new_table, table, false)) + { + /* Entries transferred successfully; tie up the loose ends. */ + free (table->bucket); + table->bucket = new_table->bucket; + table->bucket_limit = new_table->bucket_limit; + table->n_buckets = new_table->n_buckets; + table->n_buckets_used = new_table->n_buckets_used; + table->free_entry_list = new_table->free_entry_list; + /* table->n_entries and table->entry_stack already hold their value. */ + return true; + } + + /* We've allocated new_table->bucket (and possibly some entries), + exhausted the free list, and moved some but not all entries into + new_table. We must undo the partial move before returning + failure. The only way to get into this situation is if new_table + uses fewer buckets than the old table, so we will reclaim some + free entries as overflows in the new table are put back into + distinct buckets in the old table. + + There are some pathological cases where a single pass through the + table requires more intermediate overflow entries than using two + passes. Two passes give worse cache performance and takes + longer, but at this point, we're already out of memory, so slow + and safe is better than failure. */ + int saved_errno = errno; + table->free_entry_list = new_table->free_entry_list; + if (! (transfer_entries (table, new_table, true) + && transfer_entries (table, new_table, false))) + abort (); + /* table->n_entries already holds its value. */ + free (new_table->bucket); + errno = saved_errno; + return false; +} + +int +hash_insert_if_absent (Hash_table *table, void const *entry, + void const **matched_ent) +{ + + /* The caller cannot insert a NULL entry, since hash_lookup returns NULL + to indicate "not found", and find_entry uses "bucket->data == NULL" + to indicate an empty bucket. */ + if (! entry) + abort (); + + /* If there's a matching entry already in the table, return that. */ + struct hash_entry *bucket; + void *data = find_entry (table, entry, &bucket, false); + if (data != NULL) + { + if (matched_ent) + *matched_ent = data; + return 0; + } + + /* If the growth threshold of the buckets in use has been reached, increase + the table size and rehash. There's no point in checking the number of + entries: if the hashing function is ill-conditioned, rehashing is not + likely to improve it. */ + + if (table->n_buckets_used + > table->tuning->growth_threshold * table->n_buckets) + { + /* Check more fully, before starting real work. If tuning arguments + became invalid, the second check will rely on proper defaults. */ + check_tuning (table); + if (table->n_buckets_used + > table->tuning->growth_threshold * table->n_buckets) + { + const Hash_tuning *tuning = table->tuning; + float candidate = + (tuning->is_n_buckets + ? (table->n_buckets * tuning->growth_factor) + : (table->n_buckets * tuning->growth_factor + * tuning->growth_threshold)); + + if ((float) SIZE_MAX <= candidate) + { + errno = ENOMEM; + return -1; + } + + /* If the rehash fails, arrange to return NULL. */ + if (!hash_rehash (table, candidate)) + return -1; + + /* Update the bucket we are interested in. */ + if (find_entry (table, entry, &bucket, false) != NULL) + abort (); + } + } + + /* ENTRY is not matched, it should be inserted. */ + + if (bucket->data) + { + struct hash_entry *new_entry = allocate_entry (table); + + if (new_entry == NULL) + return -1; + + /* Add ENTRY in the overflow of the bucket. */ + + new_entry->data = (void *) entry; + new_entry->next = bucket->next; + bucket->next = new_entry; + table->n_entries++; + return 1; + } + + /* Add ENTRY right in the bucket head. */ + + bucket->data = (void *) entry; + table->n_entries++; + table->n_buckets_used++; + + return 1; +} + +void * +hash_insert (Hash_table *table, void const *entry) +{ + void const *matched_ent; + int err = hash_insert_if_absent (table, entry, &matched_ent); + return (err == -1 + ? NULL + : (void *) (err == 0 ? matched_ent : entry)); +} + +void * +hash_remove (Hash_table *table, const void *entry) +{ + struct hash_entry *bucket; + void *data = find_entry (table, entry, &bucket, true); + if (!data) + return NULL; + + table->n_entries--; + if (!bucket->data) + { + table->n_buckets_used--; + + /* If the shrink threshold of the buckets in use has been reached, + rehash into a smaller table. */ + + if (table->n_buckets_used + < table->tuning->shrink_threshold * table->n_buckets) + { + /* Check more fully, before starting real work. If tuning arguments + became invalid, the second check will rely on proper defaults. */ + check_tuning (table); + if (table->n_buckets_used + < table->tuning->shrink_threshold * table->n_buckets) + { + const Hash_tuning *tuning = table->tuning; + size_t candidate = + (tuning->is_n_buckets + ? table->n_buckets * tuning->shrink_factor + : (table->n_buckets * tuning->shrink_factor + * tuning->growth_threshold)); + + if (!hash_rehash (table, candidate)) + { + /* Failure to allocate memory in an attempt to + shrink the table is not fatal. But since memory + is low, we can at least be kind and free any + spare entries, rather than keeping them tied up + in the free entry list. */ +#if ! USE_OBSTACK + struct hash_entry *cursor = table->free_entry_list; + struct hash_entry *next; + while (cursor) + { + next = cursor->next; + free (cursor); + cursor = next; + } + table->free_entry_list = NULL; +#endif + } + } + } + } + + return data; +} + +/* Testing. */ + +#if TESTING + +void +hash_print (const Hash_table *table) +{ + for (struct hash_entry *bucket = (struct hash_entry *) table->bucket; + bucket < table->bucket_limit; + bucket++) + { + if (bucket) + printf ("%lu:\n", (unsigned long int) (bucket - table->bucket)); + + for (struct hash_entry *cursor = bucket; cursor; cursor = cursor->next) + { + char const *s = cursor->data; + /* FIXME */ + if (s) + printf (" %s\n", s); + } + } +} + +#endif /* TESTING */ diff --git a/misc/next-prime.c b/misc/next-prime.c new file mode 100644 index 0000000000..62fbf94a47 --- /dev/null +++ b/misc/next-prime.c @@ -0,0 +1,64 @@ +/* Finding the next prime >= a given small integer. + Copyright (C) 1995-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 . */ + +#include +#ifdef _LIBC +# include +#endif + +/* Specification. */ +#include "next-prime.h" + +#include /* for SIZE_MAX */ + +/* Return true if CANDIDATE is a prime number or 1. + CANDIDATE should be an odd number >= 1. */ +static bool _GL_ATTRIBUTE_CONST +is_prime (size_t candidate) +{ + size_t divisor = 3; + size_t square = divisor * divisor; + + for (;;) + { + if (square > candidate) + return true; + if ((candidate % divisor) == 0) + return false; + /* Increment divisor by 2. */ + divisor++; + square += 4 * divisor; + divisor++; + } +} + +size_t _GL_ATTRIBUTE_CONST +next_prime (size_t candidate) +{ +#if !defined IN_LIBGETTEXTLIB + /* Skip small primes. */ + if (candidate < 10) + candidate = 10; +#endif + + /* Make it definitely odd. */ + candidate |= 1; + + while (SIZE_MAX != candidate && !is_prime (candidate)) + candidate += 2; + + return candidate; +} diff --git a/sysdeps/mach/hurd/opendir.c b/sysdeps/mach/hurd/opendir.c index ee005cc371..e610236a58 100644 --- a/sysdeps/mach/hurd/opendir.c +++ b/sysdeps/mach/hurd/opendir.c @@ -66,14 +66,15 @@ _hurd_fd_opendir (struct hurd_fd *d) DIR * -__opendirat (int dfd, const char *name) +__opendirat (int dfd, const char *name, int extra_flags, int *pnew_fd) { if (name[0] == '\0') /* POSIX.1-1990 says an empty name gets ENOENT; but `open' might like it fine. */ return __hurd_fail (ENOENT), NULL; - int flags = O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC; + int flags = O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC + | extra_flags; int fd; #if IS_IN (rtld) assert (dfd == AT_FDCWD); @@ -88,6 +89,8 @@ __opendirat (int dfd, const char *name) DIR *dirp = _hurd_fd_opendir (_hurd_fd_get (fd)); if (dirp == NULL) __close (fd); + else if (pnew_fd != NULL) + *pnew_fd = fd; return dirp; } diff --git a/sysdeps/unix/sysv/linux/opendir.c b/sysdeps/unix/sysv/linux/opendir.c index ceaaefe2bb..9c41a8ff3e 100644 --- a/sysdeps/unix/sysv/linux/opendir.c +++ b/sysdeps/unix/sysv/linux/opendir.c @@ -66,12 +66,24 @@ opendir_tail (int fd) #if IS_IN (libc) DIR * -__opendirat (int dfd, const char *name) +__opendirat (int dfd, const char *name, int extra_flags, int *pnew_fd) { if (__glibc_unlikely (invalid_name (name))) return NULL; - return opendir_tail (__openat_nocancel (dfd, name, opendir_oflags)); + int open_oflags = opendir_oflags | extra_flags; + int new_fd = __openat_nocancel (dfd, name, open_oflags); + if (new_fd < 0) + return NULL; + DIR *dirp = opendir_tail (new_fd); + if (dirp == NULL) + { + __close_nocancel_nostatus (new_fd); + return NULL; + } + else if (pnew_fd != NULL) + *pnew_fd = new_fd; + return dirp; } #endif