diff --git a/tools/gnulib/patches/640-mem-hash-map.patch b/tools/gnulib/patches/640-mem-hash-map.patch new file mode 100644 index 0000000000..aaebe545fa --- /dev/null +++ b/tools/gnulib/patches/640-mem-hash-map.patch @@ -0,0 +1,494 @@ +From 5a842672e79a7a5f6be837c483be4f9901a4ecc0 Mon Sep 17 00:00:00 2001 +From: Bruno Haible +Date: Wed, 30 Apr 2025 03:19:10 +0200 +Subject: [PATCH] New module mem-hash-map. + +* lib/mem-hash-map.h: New file, from GNU gettext. +* lib/mem-hash-map.c: New file, from GNU gettext. +* modules/mem-hash-map: New file, from GNU gettext. +--- + ChangeLog | 7 + + lib/mem-hash-map.c | 352 +++++++++++++++++++++++++++++++++++++++++++ + lib/mem-hash-map.h | 90 +++++++++++ + modules/mem-hash-map | 25 +++ + 4 files changed, 474 insertions(+) + create mode 100644 lib/mem-hash-map.c + create mode 100644 lib/mem-hash-map.h + create mode 100644 modules/mem-hash-map + +--- /dev/null ++++ b/lib/mem-hash-map.c +@@ -0,0 +1,352 @@ ++/* Simple hash table (no removals) where the keys are memory blocks. ++ Copyright (C) 1994-2025 Free Software Foundation, Inc. ++ Written by Ulrich Drepper , October 1994. ++ ++ This file is free software: you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published ++ by the Free Software Foundation, either version 3 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 General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program. If not, see . */ ++ ++#include ++ ++/* Specification. */ ++#include "mem-hash-map.h" ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "next-prime.h" ++ ++/* Since this simple implementation of hash tables allows only insertion, no ++ removal of entries, the right data structure for the memory holding all keys ++ is an obstack. */ ++#include "obstack.h" ++ ++/* Use checked memory allocation. */ ++#include "xalloc.h" ++ ++#define obstack_chunk_alloc xmalloc ++#define obstack_chunk_free free ++ ++ ++typedef struct hash_entry ++{ ++ size_t used; /* Hash code of the key, or 0 for an unused entry. */ ++ const void *key; /* Key. */ ++ size_t keylen; ++ void *data; /* Value. */ ++ struct hash_entry *next; ++} ++hash_entry; ++ ++ ++/* Initialize a hash table. INIT_SIZE > 1 is the initial number of available ++ entries. ++ Return 0 always. */ ++int ++hash_init (hash_table *htab, size_t init_size) ++{ ++ /* We need the size to be a prime. */ ++ init_size = next_prime (init_size); ++ ++ /* Initialize the data structure. */ ++ htab->size = init_size; ++ htab->filled = 0; ++ htab->first = NULL; ++ htab->table = XCALLOC (init_size + 1, hash_entry); ++ ++ obstack_init (&htab->mem_pool); ++ ++ return 0; ++} ++ ++ ++/* Delete a hash table's contents. ++ Return 0 always. */ ++int ++hash_destroy (hash_table *htab) ++{ ++ free (htab->table); ++ obstack_free (&htab->mem_pool, NULL); ++ return 0; ++} ++ ++ ++/* Compute a hash code for a key consisting of KEYLEN bytes starting at KEY ++ in memory. */ ++static size_t ++compute_hashval (const void *key, size_t keylen) ++{ ++ size_t cnt; ++ size_t hval; ++ ++ /* Compute the hash value for the given string. The algorithm ++ is taken from [Aho,Sethi,Ullman], fixed according to ++ https://haible.de/bruno/hashfunc.html. */ ++ cnt = 0; ++ hval = keylen; ++ while (cnt < keylen) ++ { ++ hval = (hval << 9) | (hval >> (sizeof (size_t) * CHAR_BIT - 9)); ++ hval += (size_t) *(((const char *) key) + cnt++); ++ } ++ return hval != 0 ? hval : ~((size_t) 0); ++} ++ ++ ++/* References: ++ [Aho,Sethi,Ullman] Compilers: Principles, Techniques and Tools, 1986 ++ [Knuth] The Art of Computer Programming, part3 (6.4) */ ++ ++/* Look up a given key in the hash table. ++ Return the index of the entry, if present, or otherwise the index a free ++ entry where it could be inserted. */ ++static size_t ++lookup (const hash_table *htab, ++ const void *key, size_t keylen, ++ size_t hval) ++{ ++ size_t hash; ++ size_t idx; ++ hash_entry *table = htab->table; ++ ++ /* First hash function: simply take the modul but prevent zero. */ ++ hash = 1 + hval % htab->size; ++ ++ idx = hash; ++ ++ if (table[idx].used) ++ { ++ if (table[idx].used == hval && table[idx].keylen == keylen ++ && memcmp (table[idx].key, key, keylen) == 0) ++ return idx; ++ ++ /* Second hash function as suggested in [Knuth]. */ ++ hash = 1 + hval % (htab->size - 2); ++ ++ do ++ { ++ if (idx <= hash) ++ idx = htab->size + idx - hash; ++ else ++ idx -= hash; ++ ++ /* If entry is found use it. */ ++ if (table[idx].used == hval && table[idx].keylen == keylen ++ && memcmp (table[idx].key, key, keylen) == 0) ++ return idx; ++ } ++ while (table[idx].used); ++ } ++ return idx; ++} ++ ++ ++/* Look up the value of a key in the given table. ++ If found, return 0 and set *RESULT to it. Otherwise return -1. */ ++int ++hash_find_entry (const hash_table *htab, const void *key, size_t keylen, ++ void **result) ++{ ++ hash_entry *table = htab->table; ++ size_t idx = lookup (htab, key, keylen, compute_hashval (key, keylen)); ++ ++ if (table[idx].used == 0) ++ return -1; ++ ++ *result = table[idx].data; ++ return 0; ++} ++ ++ ++/* Insert the pair (KEY[0..KEYLEN-1], DATA) in the hash table at index IDX. ++ HVAL is the key's hash code. IDX depends on it. The table entry at index ++ IDX is known to be unused. */ ++static void ++insert_entry_2 (hash_table *htab, ++ const void *key, size_t keylen, ++ size_t hval, size_t idx, void *data) ++{ ++ hash_entry *table = htab->table; ++ ++ table[idx].used = hval; ++ table[idx].key = key; ++ table[idx].keylen = keylen; ++ table[idx].data = data; ++ ++ /* List the new value in the list. */ ++ if (htab->first == NULL) ++ { ++ table[idx].next = &table[idx]; ++ htab->first = &table[idx]; ++ } ++ else ++ { ++ table[idx].next = htab->first->next; ++ htab->first->next = &table[idx]; ++ htab->first = &table[idx]; ++ } ++ ++ ++htab->filled; ++} ++ ++ ++/* Grow the hash table. */ ++static void ++resize (hash_table *htab) ++{ ++ size_t old_size = htab->size; ++ hash_entry *table = htab->table; ++ size_t idx; ++ ++ htab->size = next_prime (htab->size * 2); ++ htab->filled = 0; ++ htab->first = NULL; ++ htab->table = XCALLOC (1 + htab->size, hash_entry); ++ ++ for (idx = 1; idx <= old_size; ++idx) ++ if (table[idx].used) ++ insert_entry_2 (htab, table[idx].key, table[idx].keylen, ++ table[idx].used, ++ lookup (htab, table[idx].key, table[idx].keylen, ++ table[idx].used), ++ table[idx].data); ++ ++ free (table); ++} ++ ++ ++/* Try to insert the pair (KEY[0..KEYLEN-1], DATA) in the hash table. ++ Return non-NULL (more precisely, the address of the KEY inside the table's ++ memory pool) if successful, or NULL if there is already an entry with the ++ given key. */ ++const void * ++hash_insert_entry (hash_table *htab, ++ const void *key, size_t keylen, ++ void *data) ++{ ++ size_t hval = compute_hashval (key, keylen); ++ hash_entry *table = htab->table; ++ size_t idx = lookup (htab, key, keylen, hval); ++ ++ if (table[idx].used) ++ /* We don't want to overwrite the old value. */ ++ return NULL; ++ else ++ { ++ /* An empty bucket has been found. */ ++ void *keycopy = obstack_copy (&htab->mem_pool, key, keylen); ++ insert_entry_2 (htab, keycopy, keylen, hval, idx, data); ++ if (100 * htab->filled > 75 * htab->size) ++ /* Table is filled more than 75%. Resize the table. */ ++ resize (htab); ++ return keycopy; ++ } ++} ++ ++ ++/* Insert the pair (KEY[0..KEYLEN-1], DATA) in the hash table. ++ Return 0. */ ++int ++hash_set_value (hash_table *htab, ++ const void *key, size_t keylen, ++ void *data) ++{ ++ size_t hval = compute_hashval (key, keylen); ++ hash_entry *table = htab->table; ++ size_t idx = lookup (htab, key, keylen, hval); ++ ++ if (table[idx].used) ++ { ++ /* Overwrite the old value. */ ++ table[idx].data = data; ++ return 0; ++ } ++ else ++ { ++ /* An empty bucket has been found. */ ++ void *keycopy = obstack_copy (&htab->mem_pool, key, keylen); ++ insert_entry_2 (htab, keycopy, keylen, hval, idx, data); ++ if (100 * htab->filled > 75 * htab->size) ++ /* Table is filled more than 75%. Resize the table. */ ++ resize (htab); ++ return 0; ++ } ++} ++ ++ ++/* Steps *PTR forward to the next used entry in the given hash table. *PTR ++ should be initially set to NULL. Store information about the next entry ++ in *KEY, *KEYLEN, *DATA. ++ Return 0 normally, -1 when the whole hash table has been traversed. */ ++int ++hash_iterate (hash_table *htab, void **ptr, const void **key, size_t *keylen, ++ void **data) ++{ ++ hash_entry *curr; ++ ++ if (*ptr == NULL) ++ { ++ if (htab->first == NULL) ++ return -1; ++ curr = htab->first; ++ } ++ else ++ { ++ if (*ptr == htab->first) ++ return -1; ++ curr = (hash_entry *) *ptr; ++ } ++ curr = curr->next; ++ *ptr = (void *) curr; ++ ++ *key = curr->key; ++ *keylen = curr->keylen; ++ *data = curr->data; ++ return 0; ++} ++ ++ ++/* Steps *PTR forward to the next used entry in the given hash table. *PTR ++ should be initially set to NULL. Store information about the next entry ++ in *KEY, *KEYLEN, *DATAP. *DATAP is set to point to the storage of the ++ value; modifying **DATAP will modify the value of the entry. ++ Return 0 normally, -1 when the whole hash table has been traversed. */ ++int ++hash_iterate_modify (hash_table *htab, void **ptr, ++ const void **key, size_t *keylen, ++ void ***datap) ++{ ++ hash_entry *curr; ++ ++ if (*ptr == NULL) ++ { ++ if (htab->first == NULL) ++ return -1; ++ curr = htab->first; ++ } ++ else ++ { ++ if (*ptr == htab->first) ++ return -1; ++ curr = (hash_entry *) *ptr; ++ } ++ curr = curr->next; ++ *ptr = (void *) curr; ++ ++ *key = curr->key; ++ *keylen = curr->keylen; ++ *datap = &curr->data; ++ return 0; ++} +--- /dev/null ++++ b/lib/mem-hash-map.h +@@ -0,0 +1,90 @@ ++/* Simple hash table (no removals) where the keys are memory blocks. ++ Copyright (C) 1995-2025 Free Software Foundation, Inc. ++ ++ This file is free software: you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published ++ by the Free Software Foundation, either version 3 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 General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program. If not, see . */ ++ ++#ifndef _GL_MEM_HASH_MAP_H ++#define _GL_MEM_HASH_MAP_H ++ ++#include ++ ++#include "obstack.h" ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++struct hash_entry; ++ ++typedef struct hash_table ++{ ++ size_t size; /* Number of allocated entries. */ ++ size_t filled; /* Number of used entries. */ ++ struct hash_entry *first; /* Pointer to head of list of entries. */ ++ struct hash_entry *table; /* Pointer to array of entries. */ ++ struct obstack mem_pool; /* Memory pool holding the keys. */ ++} ++hash_table; ++ ++/* Initialize a hash table. INIT_SIZE > 1 is the initial number of available ++ entries. ++ Return 0 always. */ ++extern int hash_init (hash_table *htab, size_t init_size); ++ ++/* Delete a hash table's contents. ++ Return 0 always. */ ++extern int hash_destroy (hash_table *htab); ++ ++/* Look up the value of a key in the given table. ++ If found, return 0 and set *RESULT to it. Otherwise return -1. */ ++extern int hash_find_entry (const hash_table *htab, ++ const void *key, size_t keylen, ++ void **result); ++ ++/* Try to insert the pair (KEY[0..KEYLEN-1], DATA) in the hash table. ++ Return non-NULL (more precisely, the address of the KEY inside the table's ++ memory pool) if successful, or NULL if there is already an entry with the ++ given key. */ ++extern const void * hash_insert_entry (hash_table *htab, ++ const void *key, size_t keylen, ++ void *data); ++ ++/* Insert the pair (KEY[0..KEYLEN-1], DATA) in the hash table. ++ Return 0. */ ++extern int hash_set_value (hash_table *htab, ++ const void *key, size_t keylen, ++ void *data); ++ ++/* Steps *PTR forward to the next used entry in the given hash table. *PTR ++ should be initially set to NULL. Store information about the next entry ++ in *KEY, *KEYLEN, *DATA. ++ Return 0 normally, -1 when the whole hash table has been traversed. */ ++extern int hash_iterate (hash_table *htab, void **ptr, ++ const void **key, size_t *keylen, ++ void **data); ++ ++/* Steps *PTR forward to the next used entry in the given hash table. *PTR ++ should be initially set to NULL. Store information about the next entry ++ in *KEY, *KEYLEN, *DATAP. *DATAP is set to point to the storage of the ++ value; modifying **DATAP will modify the value of the entry. ++ Return 0 normally, -1 when the whole hash table has been traversed. */ ++extern int hash_iterate_modify (hash_table *htab, void **ptr, ++ const void **key, size_t *keylen, ++ void ***datap); ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif /* not _GL_MEM_HASH_MAP_H */ +--- /dev/null ++++ b/modules/mem-hash-map +@@ -0,0 +1,25 @@ ++Description: ++Simple hash table (no removals) where the keys are memory blocks. ++ ++Files: ++lib/mem-hash-map.h ++lib/mem-hash-map.c ++ ++Depends-on: ++next-prime ++obstack ++xalloc ++ ++configure.ac: ++ ++Makefile.am: ++lib_SOURCES += mem-hash-map.h mem-hash-map.c ++ ++Include: ++"mem-hash-map.h" ++ ++License: ++GPL ++ ++Maintainer: ++Bruno Haible diff --git a/tools/gnulib/patches/645-next-prime.patch b/tools/gnulib/patches/645-next-prime.patch new file mode 100644 index 0000000000..66f482f5ec --- /dev/null +++ b/tools/gnulib/patches/645-next-prime.patch @@ -0,0 +1,218 @@ +From 0b953ba82830f51ce9b939700705d238f9b0c0ba Mon Sep 17 00:00:00 2001 +From: Bruno Haible +Date: Wed, 30 Apr 2025 01:52:17 +0200 +Subject: [PATCH] New module next-prime. + +* lib/next-prime.h: New file, based on lib/hash.c. +* lib/next-prime.c: New file, based on lib/hash.c. +* modules/next-prime: New file. +* lib/hash.c: Include next-prime.h. +(is_prime, next_prime): Remove functions. +* modules/hash (Depends-on): Add next-prime. +--- + ChangeLog | 10 +++++++++ + lib/hash.c | 39 +------------------------------- + lib/next-prime.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++ + lib/next-prime.h | 41 +++++++++++++++++++++++++++++++++ + modules/hash | 1 + + modules/next-prime | 24 ++++++++++++++++++++ + 6 files changed, 133 insertions(+), 38 deletions(-) + create mode 100644 lib/next-prime.c + create mode 100644 lib/next-prime.h + create mode 100644 modules/next-prime + +--- a/lib/hash.c ++++ b/lib/hash.c +@@ -27,6 +27,7 @@ + #include "hash.h" + + #include "bitrotate.h" ++#include "next-prime.h" + #include "xalloc-oversized.h" + + #include +@@ -390,44 +391,6 @@ hash_string (const char *string, size_t + + #endif /* not USE_DIFF_HASH */ + +-/* Return true if CANDIDATE is a prime number. CANDIDATE should be an odd +- number at least equal to 11. */ +- +-static bool _GL_ATTRIBUTE_CONST +-is_prime (size_t candidate) +-{ +- size_t divisor = 3; +- size_t square = divisor * divisor; +- +- while (square < candidate && (candidate % divisor)) +- { +- divisor++; +- square += 4 * divisor; +- divisor++; +- } +- +- return (candidate % divisor ? true : false); +-} +- +-/* Round a given CANDIDATE number up to the nearest prime, and return that +- prime. Primes lower than 10 are merely skipped. */ +- +-static size_t _GL_ATTRIBUTE_CONST +-next_prime (size_t candidate) +-{ +- /* Skip small primes. */ +- if (candidate < 10) +- candidate = 10; +- +- /* Make it definitely odd. */ +- candidate |= 1; +- +- while (SIZE_MAX != candidate && !is_prime (candidate)) +- candidate += 2; +- +- return candidate; +-} +- + void + hash_reset_tuning (Hash_tuning *tuning) + { +--- /dev/null ++++ b/lib/next-prime.c +@@ -0,0 +1,56 @@ ++/* Finding the next prime >= a given small integer. ++ Copyright (C) 1995-2025 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 ++ ++/* Specification. */ ++#include "next-prime.h" ++ ++#include /* for SIZE_MAX */ ++ ++/* Return true if CANDIDATE is a prime number. CANDIDATE should be an odd ++ number at least equal to 11. */ ++static bool _GL_ATTRIBUTE_CONST ++is_prime (size_t candidate) ++{ ++ size_t divisor = 3; ++ size_t square = divisor * divisor; ++ ++ while (square < candidate && (candidate % divisor)) ++ { ++ divisor++; ++ square += 4 * divisor; ++ divisor++; ++ } ++ ++ return (candidate % divisor ? true : false); ++} ++ ++size_t _GL_ATTRIBUTE_CONST ++next_prime (size_t candidate) ++{ ++ /* Skip small primes. */ ++ if (candidate < 10) ++ candidate = 10; ++ ++ /* Make it definitely odd. */ ++ candidate |= 1; ++ ++ while (SIZE_MAX != candidate && !is_prime (candidate)) ++ candidate += 2; ++ ++ return candidate; ++} +--- /dev/null ++++ b/lib/next-prime.h +@@ -0,0 +1,41 @@ ++/* Finding the next prime >= a given small integer. ++ Copyright (C) 1995-2025 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 !_GL_CONFIG_H_INCLUDED ++ #error "Please include config.h first." ++#endif ++ ++#include ++ ++#ifdef __cplusplus ++extern "C" { ++#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); ++ ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif /* _GL_NEXT_PRIME_H */ +--- a/modules/hash ++++ b/modules/hash +@@ -10,6 +10,7 @@ bitrotate + calloc-posix + free-posix + malloc-posix ++next-prime + bool + stdint-h + xalloc-oversized +--- /dev/null ++++ b/modules/next-prime +@@ -0,0 +1,24 @@ ++Description: ++Finding the next prime >= a given small integer. ++ ++Files: ++lib/next-prime.h ++lib/next-prime.c ++ ++Depends-on: ++bool ++stdint-h ++ ++configure.ac: ++ ++Makefile.am: ++lib_SOURCES += next-prime.h next-prime.c ++ ++Include: ++"next-prime.h" ++ ++License: ++LGPLv2+ ++ ++Maintainer: ++all diff --git a/tools/gnulib/patches/646-hashcode-string.patch b/tools/gnulib/patches/646-hashcode-string.patch new file mode 100644 index 0000000000..ee2ebd237f --- /dev/null +++ b/tools/gnulib/patches/646-hashcode-string.patch @@ -0,0 +1,294 @@ +From 64042bb91aea5f854ca8a8938e2b3f7d1935e4f1 Mon Sep 17 00:00:00 2001 +From: Bruno Haible +Date: Wed, 30 Apr 2025 12:47:37 +0200 +Subject: [PATCH] New module hashcode-string1. + +* lib/hashcode-string1.h: New file. +* lib/hashcode-string1.c: New file, based on lib/hash.c. +* modules/hashcode-string1: New file. +* lib/hash.h: Include hashcode-string1.h. +(hash_string): Remove declaration. +* lib/hash.c (hash_string): Remove function. +* modules/hash (Depends-on): Add hashcode-string1. +* lib/exclude.c: Include hashcode-string1.h. +* modules/exclude (Depends-on): Add hashcode-string1. +--- + ChangeLog | 13 +++++++++ + lib/exclude.c | 1 + + lib/hash.c | 59 ++++++-------------------------------- + lib/hash.h | 11 +++---- + lib/hashcode-string1.c | 62 ++++++++++++++++++++++++++++++++++++++++ + lib/hashcode-string1.h | 38 ++++++++++++++++++++++++ + modules/exclude | 1 + + modules/hash | 1 + + modules/hashcode-string1 | 24 ++++++++++++++++ + 9 files changed, 154 insertions(+), 56 deletions(-) + create mode 100644 lib/hashcode-string1.c + create mode 100644 lib/hashcode-string1.h + create mode 100644 modules/hashcode-string1 + +--- a/lib/exclude.c ++++ b/lib/exclude.c +@@ -36,6 +36,7 @@ + #include "filename.h" + #include + #include "hash.h" ++#include "hashcode-string1.h" + #if GNULIB_MCEL_PREFER + # include "mcel.h" + #else +--- a/lib/hash.c ++++ b/lib/hash.c +@@ -345,57 +345,6 @@ hash_do_for_each (const Hash_table *tabl + return counter; + } + +-/* Allocation and clean-up. */ +- +-#if USE_DIFF_HASH +- +-/* About hashings, Paul Eggert writes to me (FP), on 1994-01-01: "Please see +- B. J. McKenzie, R. Harries & T. Bell, Selecting a hashing algorithm, +- Software--practice & experience 20, 2 (Feb 1990), 209-224. Good hash +- algorithms tend to be domain-specific, so what's good for [diffutils'] io.c +- may not be good for your application." */ +- +-size_t +-hash_string (const char *string, size_t n_buckets) +-{ +-# define HASH_ONE_CHAR(Value, Byte) \ +- ((Byte) + rotl_sz (Value, 7)) +- +- size_t value = 0; +- unsigned char ch; +- +- for (; (ch = *string); string++) +- value = HASH_ONE_CHAR (value, ch); +- return value % n_buckets; +- +-# undef HASH_ONE_CHAR +-} +- +-#else /* not USE_DIFF_HASH */ +- +-/* This one comes from 'recode', and performs a bit better than the above as +- per a few experiments. It is inspired from a hashing routine found in the +- very old Cyber 'snoop', itself written in typical Greg Mansfield style. +- (By the way, what happened to this excellent man? Is he still alive?) */ +- +-size_t +-hash_string (const char *string, size_t n_buckets) +-{ +- size_t value = 0; +- unsigned char ch; +- +- for (; (ch = *string); string++) +- value = (value * 31 + ch) % n_buckets; +- return value; +-} +- +-#endif /* not USE_DIFF_HASH */ +- +-void +-hash_reset_tuning (Hash_tuning *tuning) +-{ +- *tuning = default_tuning; +-} + + /* If the user passes a NULL hasher, we hash the raw pointer. */ + static size_t +@@ -418,6 +367,14 @@ raw_comparator (const void *a, const voi + } + + ++/* Allocation and clean-up. */ ++ ++void ++hash_reset_tuning (Hash_tuning *tuning) ++{ ++ *tuning = default_tuning; ++} ++ + /* 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 +--- a/lib/hash.h ++++ b/lib/hash.h +@@ -134,11 +134,6 @@ extern size_t hash_do_for_each (const Ha + * Allocation and clean-up. + */ + +-/* Return a hash index for a NUL-terminated STRING between 0 and N_BUCKETS-1. +- This is a convenience routine for constructing other hashing functions. */ +-extern size_t hash_string (const char *string, size_t n_buckets) +- _GL_ATTRIBUTE_PURE; +- + extern void hash_reset_tuning (Hash_tuning *tuning); + + typedef size_t (*Hash_hasher) (const void *entry, size_t table_size); +@@ -266,6 +261,12 @@ extern void *hash_remove (Hash_table *ta + _GL_ATTRIBUTE_DEPRECATED + extern void *hash_delete (Hash_table *table, const void *entry); + ++ ++# if GNULIB_HASHCODE_STRING1 ++/* Include declarations of module 'hashcode-string1'. */ ++# include "hashcode-string1.h" ++# endif ++ + # ifdef __cplusplus + } + # endif +--- /dev/null ++++ b/lib/hashcode-string1.c +@@ -0,0 +1,62 @@ ++/* hashcode-string1.c -- compute a hash value from a NUL-terminated string. ++ ++ Copyright (C) 1998-2004, 2006-2007, 2009-2025 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 ++ ++/* Specification. */ ++#include "hashcode-string1.h" ++ ++#if USE_DIFF_HASH ++ ++# include "bitrotate.h" ++ ++/* About hashings, Paul Eggert writes to me (FP), on 1994-01-01: "Please see ++ B. J. McKenzie, R. Harries & T. Bell, Selecting a hashing algorithm, ++ Software--practice & experience 20, 2 (Feb 1990), 209-224. Good hash ++ algorithms tend to be domain-specific, so what's good for [diffutils'] io.c ++ may not be good for your application." */ ++ ++size_t ++hash_string (const char *string, size_t tablesize) ++{ ++ size_t value = 0; ++ unsigned char ch; ++ ++ for (; (ch = *string); string++) ++ value = ch + rotl_sz (value, 7); ++ return value % tablesize; ++} ++ ++#else /* not USE_DIFF_HASH */ ++ ++/* This one comes from 'recode', and performs a bit better than the above as ++ per a few experiments. It is inspired from a hashing routine found in the ++ very old Cyber 'snoop', itself written in typical Greg Mansfield style. ++ (By the way, what happened to this excellent man? Is he still alive?) */ ++ ++size_t ++hash_string (const char *string, size_t tablesize) ++{ ++ size_t value = 0; ++ unsigned char ch; ++ ++ for (; (ch = *string); string++) ++ value = (value * 31 + ch) % tablesize; ++ return value; ++} ++ ++#endif /* not USE_DIFF_HASH */ +--- /dev/null ++++ b/lib/hashcode-string1.h +@@ -0,0 +1,38 @@ ++/* hashcode-string1.h -- declaration for a simple hash function ++ Copyright (C) 1998-2004, 2006-2007, 2009-2025 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 . */ ++ ++/* This file uses _GL_ATTRIBUTE_PURE. */ ++#if !_GL_CONFIG_H_INCLUDED ++ #error "Please include config.h first." ++#endif ++ ++#include ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++ ++/* Compute a hash code for a NUL-terminated string S, ++ and return the hash code modulo TABLESIZE. ++ The result is platform dependent: it depends on the size of the 'size_t' ++ type. */ ++extern size_t hash_string (char const *s, size_t tablesize) _GL_ATTRIBUTE_PURE; ++ ++ ++#ifdef __cplusplus ++} ++#endif +--- a/modules/exclude ++++ b/modules/exclude +@@ -12,6 +12,7 @@ filename + fnmatch + fopen-gnu + hash ++hashcode-string1 + mbscasecmp + mbuiter [test "$GNULIB_MCEL_PREFER" != yes] + nullptr +--- a/modules/hash ++++ b/modules/hash +@@ -14,6 +14,7 @@ next-prime + bool + stdint-h + xalloc-oversized ++hashcode-string1 + + configure.ac: + +--- /dev/null ++++ b/modules/hashcode-string1 +@@ -0,0 +1,24 @@ ++Description: ++Compute a hash value for a NUL-terminated string. ++ ++Files: ++lib/hashcode-string1.h ++lib/hashcode-string1.c ++ ++Depends-on: ++bitrotate ++ ++configure.ac: ++gl_MODULE_INDICATOR([hashcode-string1]) ++ ++Makefile.am: ++lib_SOURCES += hashcode-string1.h hashcode-string1.c ++ ++Include: ++"hashcode-string1.h" ++ ++License: ++LGPLv2+ ++ ++Maintainer: ++Jim Meyering diff --git a/tools/gnulib/patches/647-hashkey-string.patch b/tools/gnulib/patches/647-hashkey-string.patch new file mode 100644 index 0000000000..d0cd0a6311 --- /dev/null +++ b/tools/gnulib/patches/647-hashkey-string.patch @@ -0,0 +1,215 @@ +From 52738dcd0f522b16653cc8b21adfcb758702f2ab Mon Sep 17 00:00:00 2001 +From: Bruno Haible +Date: Wed, 30 Apr 2025 01:20:17 +0200 +Subject: [PATCH] New module hashkey-string. + +* lib/hashkey-string.h: New file. +* lib/hashkey-string.c: New file, based on lib/clean-temp-simple.c. +* modules/hashkey-string: New file. +* lib/clean-temp-simple.c: Include hashkey-string.h. Don't include +. +(clean_temp_string_equals, clean_temp_string_hash): Remove functions. +(SIZE_BITS): Remove macro. +(register_temporary_file): Use hashkey_string_equals and +hashkey_string_hash. +* modules/clean-temp-simple (Depends-on): Add hashkey-string. +--- + ChangeLog | 14 ++++++++++++ + lib/clean-temp-simple.c | 33 +++------------------------ + lib/hashkey-string.c | 48 +++++++++++++++++++++++++++++++++++++++ + lib/hashkey-string.h | 35 ++++++++++++++++++++++++++++ + modules/clean-temp-simple | 1 + + modules/hashkey-string | 23 +++++++++++++++++++ + 6 files changed, 124 insertions(+), 30 deletions(-) + create mode 100644 lib/hashkey-string.c + create mode 100644 lib/hashkey-string.h + create mode 100644 modules/hashkey-string + +--- a/lib/clean-temp-simple.c ++++ b/lib/clean-temp-simple.c +@@ -22,7 +22,6 @@ + #include "clean-temp-private.h" + + #include +-#include + #include + #include + #include +@@ -36,6 +35,7 @@ + #include "thread-optim.h" + #include "gl_list.h" + #include "gl_linkedhash_list.h" ++#include "hashkey-string.h" + #include "gettext.h" + + #define _(msgid) dgettext ("gnulib", msgid) +@@ -106,33 +106,6 @@ gl_list_t /* */ volatil + asynchronous signal. + */ + +-/* String equality and hash code functions used by the lists. */ +- +-bool +-clean_temp_string_equals (const void *x1, const void *x2) +-{ +- const char *s1 = (const char *) x1; +- const char *s2 = (const char *) x2; +- return strcmp (s1, s2) == 0; +-} +- +-#define SIZE_BITS (sizeof (size_t) * CHAR_BIT) +- +-/* A hash function for NUL-terminated char* strings using +- the method described by Bruno Haible. +- See https://www.haible.de/bruno/hashfunc.html. */ +-size_t +-clean_temp_string_hash (const void *x) +-{ +- const char *s = (const char *) x; +- size_t h = 0; +- +- for (; *s; s++) +- h = *s + ((h << 9) | (h >> (SIZE_BITS - 9))); +- +- return h; +-} +- + + /* The set of fatal signal handlers. + Cached here because we are not allowed to call get_fatal_signal_set () +@@ -326,8 +299,8 @@ register_temporary_file (const char *abs + } + file_cleanup_list = + gl_list_nx_create_empty (GL_LINKEDHASH_LIST, +- clean_temp_string_equals, +- clean_temp_string_hash, ++ hashkey_string_equals, ++ hashkey_string_hash, + NULL, false); + if (file_cleanup_list == NULL) + { +--- /dev/null ++++ b/lib/hashkey-string.c +@@ -0,0 +1,48 @@ ++/* Support for using a string as a hash key. ++ Copyright (C) 2006-2025 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 ++ ++/* Specification. */ ++#include "hashkey-string.h" ++ ++#include ++#include ++ ++bool ++hashkey_string_equals (const void *x1, const void *x2) ++{ ++ const char *s1 = (const char *) x1; ++ const char *s2 = (const char *) x2; ++ return strcmp (s1, s2) == 0; ++} ++ ++#define SIZE_BITS (sizeof (size_t) * CHAR_BIT) ++ ++/* A hash function for NUL-terminated 'const char *' strings using ++ the method described by Bruno Haible. ++ See https://www.haible.de/bruno/hashfunc.html. */ ++size_t ++hashkey_string_hash (const void *x) ++{ ++ const char *s = (const char *) x; ++ size_t h = 0; ++ ++ for (; *s; s++) ++ h = *s + ((h << 9) | (h >> (SIZE_BITS - 9))); ++ ++ return h; ++} +--- /dev/null ++++ b/lib/hashkey-string.h +@@ -0,0 +1,35 @@ ++/* Support for using a string as a hash key. ++ Copyright (C) 2006-2025 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_HASHKEY_STRING_H ++#define _GL_HASHKEY_STRING_H ++ ++#include ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++/* String equality and hash code functions that operate on plain C strings ++ ('const char *'). */ ++extern bool hashkey_string_equals (const void *x1, const void *x2); ++extern size_t hashkey_string_hash (const void *x); ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif /* _GL_HASHKEY_STRING_H */ +--- a/modules/clean-temp-simple ++++ b/modules/clean-temp-simple +@@ -19,6 +19,7 @@ error + fatal-signal + rmdir + linkedhash-list ++hashkey-string + gettext-h + gnulib-i18n + +--- /dev/null ++++ b/modules/hashkey-string +@@ -0,0 +1,23 @@ ++Description: ++Support for using a string as a hash key in the hash-set and hash-map modules. ++ ++Files: ++lib/hashkey-string.h ++lib/hashkey-string.c ++ ++Depends-on: ++bool ++ ++configure.ac: ++ ++Makefile.am: ++lib_SOURCES += hashkey-string.h hashkey-string.c ++ ++Include: ++"hashkey-string.h" ++ ++License: ++LGPLv2+ ++ ++Maintainer: ++all diff --git a/tools/gnulib/patches/650-package-version.patch b/tools/gnulib/patches/650-package-version.patch new file mode 100644 index 0000000000..2e3e046736 --- /dev/null +++ b/tools/gnulib/patches/650-package-version.patch @@ -0,0 +1,176 @@ +From e518788ad085e02b046e42889039a1f671e4619a Mon Sep 17 00:00:00 2001 +From: Bruno Haible +Date: Wed, 22 Jan 2025 21:21:59 +0100 +Subject: New module 'package-version'. + +* m4/init-package-version.m4: New file, from GNU libunistring. +* modules/package-version: New file. +* modules/git-version-gen (Depends-on): Add it. +--- + ChangeLog | 7 +++ + m4/init-package-version.m4 | 124 +++++++++++++++++++++++++++++++++++++++++++++ + modules/git-version-gen | 1 + + modules/package-version | 19 +++++++ + 4 files changed, 151 insertions(+) + create mode 100644 m4/init-package-version.m4 + create mode 100644 modules/package-version + +--- /dev/null ++++ b/m4/init-package-version.m4 +@@ -0,0 +1,124 @@ ++# init-package-version.m4 ++# serial 3 ++dnl Copyright (C) 1992-2025 Free Software Foundation, Inc. ++dnl This file is free software, distributed under the terms of the GNU ++dnl General Public License. As a special exception to the GNU General ++dnl Public License, this file may be distributed as part of a program ++dnl that contains a configuration script generated by Autoconf, under ++dnl the same distribution terms as the rest of that program. ++ ++# Make it possible to pass version numbers extracted from a file in ++# $(srcdir) to autoconf. ++# ++# Autoconf insists on passing the package name and version number to ++# every generated .h file and every Makefile. This was a reasonable ++# design at times when a version number was changed only once a month. ++# Nowadays, people often assign a new version number once a week, or ++# even change it each time a 'git' commit is made. Regenerating all ++# the files that depend on configure.ac (aclocal.m4, configure, ++# config.status, config.h, all Makefiles) may take 15 minutes. These ++# delays can severely hamper development. ++# ++# An alternative is to store the version number in a file in $(srcdir) ++# that is separate from configure.ac. It can be a data file, a shell ++# script, a .m4 file, or other. The essential point is that the maintainer ++# is responsible for creating Makefile dependencies to this version file ++# for every file that needs to be rebuilt when the version changes. This ++# typically includes ++# - distributable documentation files that carry the version number, ++# but does not include ++# - aclocal.m4, configure, config.status, config.h, all Makefiles, ++# - executables. ++# ++# autoconf and automake make it hard to follow this approach: ++# ++# - If AC_INIT is used with arguments, there is a chicken-and-egg problem: ++# The arguments need to be read from a file in $(srcdir). The location ++# of $(srcdir) is only determined by AC_CONFIG_SRCDIR. AC_CONFIG_SRCDIR ++# can only appear after AC_INIT (otherwise aclocal gives an error: ++# "error: m4_defn: undefined macro: _m4_divert_diversion"). ++# Furthermore, the arguments passed to AC_INIT must be literals; for ++# example, the assignment to PACKAGE_VERSION looks like this: ++# [PACKAGE_VERSION=']AC_PACKAGE_VERSION['] ++# ++# - If AC_INIT is used without arguments: ++# Automake provides its own variables, PACKAGE and VERSION, and uses them ++# instead of PACKAGE_NAME and PACKAGE_VERSION that come from Autoconf. ++# - If AM_INIT_AUTOMAKE is used with two arguments, automake options ++# like 'silent-rules' cannot be specified. ++# - If AM_INIT_AUTOMAKE is used in its one-argument form or without ++# arguments at all, it triggers an error ++# "error: AC_INIT should be called with package and version arguments". ++# - If AM_INIT_AUTOMAKE is used in its one-argument form or without ++# arguments at all, and _AC_INIT_PACKAGE is used before it, with ++# the package and version number from the file as arguments, we get ++# a warning: "warning: AC_INIT: not a literal: $VERSION_NUMBER". ++# The arguments passed to _AC_INIT_PACKAGE must be literals. ++# ++# With the macro defined in this file, the approach can be coded like this: ++# ++# AC_INIT ++# AC_CONFIG_SRCDIR(WITNESS) ++# . $srcdir/../version.sh ++# gl_INIT_PACKAGE(PACKAGE, $VERSION_NUMBER) ++# AM_INIT_AUTOMAKE([OPTIONS]) ++# ++# and after changing version.sh, the developer can directly configure and build: ++# ++# make distclean ++# ./configure ++# make ++# ++# Some other packages use another approach: ++# ++# AC_INIT(PACKAGE, ++# m4_normalize(m4_esyscmd([. ./version.sh; echo $VERSION_NUMBER]))) ++# AC_CONFIG_SRCDIR(WITNESS) ++# AM_INIT_AUTOMAKE([OPTIONS]) ++# ++# but here, after changing version.sh, the developer must first regenerate the ++# configure file: ++# ++# make distclean ++# ./autogen.sh --skip-gnulib ++# ./configure ++# make ++# ++ ++# gl_INIT_PACKAGE(PACKAGE-NAME, VERSION) ++# -------------------------------------- ++# followed by an AM_INIT_AUTOMAKE invocation, ++# is like calling AM_INIT_AUTOMAKE(PACKAGE-NAME, VERSION) ++# except that it can use computed non-literal arguments. ++AC_DEFUN([gl_INIT_PACKAGE], ++[ ++ AC_BEFORE([$0], [AM_INIT_AUTOMAKE]) ++ dnl Redefine AM_INIT_AUTOMAKE. ++ m4_define([gl_AM_INIT_AUTOMAKE], ++ m4_bpatsubst(m4_dquote( ++ m4_bpatsubst(m4_dquote( ++ m4_bpatsubst(m4_dquote( ++ m4_defn([AM_INIT_AUTOMAKE])), ++ [AC_PACKAGE_NAME], [gl_INIT_DUMMY])), ++ [AC_PACKAGE_TARNAME], [gl_INIT_EMPTY])), ++ [AC_PACKAGE_VERSION], [gl_INIT_DUMMY]) ++ [AC_SUBST([PACKAGE], [$1]) ++ AC_SUBST([VERSION], [$2]) ++ ]) ++ m4_define([AM_INIT_AUTOMAKE], ++ m4_defn([gl_RPL_INIT_AUTOMAKE])) ++]) ++m4_define([gl_INIT_EMPTY], []) ++dnl Automake 1.16.4 no longer accepts an empty value for gl_INIT_DUMMY. ++dnl But a macro that later expands to empty works. ++m4_define([gl_INIT_DUMMY], [gl_INIT_DUMMY2]) ++m4_define([gl_INIT_DUMMY2], []) ++AC_DEFUN([gl_RPL_INIT_AUTOMAKE], [ ++ m4_ifval([$2], ++ [m4_fatal([After gl_INIT_PACKAGE, the two-argument form of AM_INIT_AUTOMAKE cannot be used.])]) ++ gl_AM_INIT_AUTOMAKE([$1 no-define]) ++ m4_if(m4_index([ $1 ], [ no-define ]), [-1], ++ [AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE", [Name of package]) ++ AC_DEFINE_UNQUOTED(VERSION, "$VERSION", [Version number of package]) ++ ]) ++]) +--- a/modules/git-version-gen ++++ b/modules/git-version-gen +@@ -5,6 +5,7 @@ Files: + build-aux/git-version-gen + + Depends-on: ++package-version + + configure.ac: + +--- /dev/null ++++ b/modules/package-version +@@ -0,0 +1,19 @@ ++Description: ++Support for a computed version string. ++ ++Files: ++m4/init-package-version.m4 ++ ++Depends-on: ++ ++configure.ac: ++ ++Makefile.am: ++ ++Include: ++ ++License: ++GPLed build tool ++ ++Maintainer: ++Bruno Haible diff --git a/tools/gnulib/patches/651-package-version-simplify.patch b/tools/gnulib/patches/651-package-version-simplify.patch new file mode 100644 index 0000000000..bed3e65c41 --- /dev/null +++ b/tools/gnulib/patches/651-package-version-simplify.patch @@ -0,0 +1,66 @@ +From bb0f82be83d43db9cd77049be32ffd0b92ab5bb7 Mon Sep 17 00:00:00 2001 +From: Bruno Haible +Date: Fri, 24 Jan 2025 22:03:29 +0100 +Subject: package-version: Simplify its use. + +Reported by Basil L. Contovounesios in +. + +* doc/package-version.texi (Propagating the package version): Recommend +to pass the usual arguments to AC_INIT. +* m4/init-package-version.m4: Likewise. +(gl_INIT_PACKAGE): Define PACKAGE_VERSION and PACKAGE_STRING as needed. +(gl_RPL_INIT_AUTOMAKE): Improve quoting. +--- + ChangeLog | 11 +++++++++++ + doc/package-version.texi | 2 +- + m4/init-package-version.m4 | 20 ++++++++++++++------ + 3 files changed, 26 insertions(+), 7 deletions(-) + +--- a/m4/init-package-version.m4 ++++ b/m4/init-package-version.m4 +@@ -1,5 +1,5 @@ + # init-package-version.m4 +-# serial 3 ++# serial 4 + dnl Copyright (C) 1992-2025 Free Software Foundation, Inc. + dnl This file is free software, distributed under the terms of the GNU + dnl General Public License. As a special exception to the GNU General +@@ -57,7 +57,7 @@ dnl the same distribution terms as the r + # + # With the macro defined in this file, the approach can be coded like this: + # +-# AC_INIT ++# AC_INIT(PACKAGE, [dummy], [MORE OPTIONS]) + # AC_CONFIG_SRCDIR(WITNESS) + # . $srcdir/../version.sh + # gl_INIT_PACKAGE(PACKAGE, $VERSION_NUMBER) +@@ -102,8 +102,16 @@ AC_DEFUN([gl_INIT_PACKAGE], + [AC_PACKAGE_NAME], [gl_INIT_DUMMY])), + [AC_PACKAGE_TARNAME], [gl_INIT_EMPTY])), + [AC_PACKAGE_VERSION], [gl_INIT_DUMMY]) +- [AC_SUBST([PACKAGE], [$1]) +- AC_SUBST([VERSION], [$2]) ++ [dnl Set variables documented in Automake. ++ AC_SUBST([PACKAGE], [$1]) ++ AC_SUBST([VERSION], ["$2"]) ++ dnl Set variables documented in Autoconf. ++ AC_SUBST([PACKAGE_VERSION], ["$2"]) ++ AC_SUBST([PACKAGE_STRING], ["$1 $2"]) ++ AC_DEFINE_UNQUOTED([PACKAGE_VERSION], ["$2"], ++ [Define to the version of this package.]) ++ AC_DEFINE_UNQUOTED([PACKAGE_STRING], ["$1 $2"], ++ [Define to the full name and version of this package.]) + ]) + m4_define([AM_INIT_AUTOMAKE], + m4_defn([gl_RPL_INIT_AUTOMAKE])) +@@ -118,7 +126,7 @@ AC_DEFUN([gl_RPL_INIT_AUTOMAKE], [ + [m4_fatal([After gl_INIT_PACKAGE, the two-argument form of AM_INIT_AUTOMAKE cannot be used.])]) + gl_AM_INIT_AUTOMAKE([$1 no-define]) + m4_if(m4_index([ $1 ], [ no-define ]), [-1], +- [AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE", [Name of package]) +- AC_DEFINE_UNQUOTED(VERSION, "$VERSION", [Version number of package]) ++ [AC_DEFINE_UNQUOTED([PACKAGE], ["$PACKAGE"], [Name of package]) ++ AC_DEFINE_UNQUOTED([VERSION], ["$VERSION"], [Version number of package]) + ]) + ]) diff --git a/tools/gnulib/patches/652-package-version-simplify-further.patch b/tools/gnulib/patches/652-package-version-simplify-further.patch new file mode 100644 index 0000000000..5458918246 --- /dev/null +++ b/tools/gnulib/patches/652-package-version-simplify-further.patch @@ -0,0 +1,89 @@ +From 48648b4b9b3fd79a5c68913deb28678bd9d8eb34 Mon Sep 17 00:00:00 2001 +From: Bruno Haible +Date: Sat, 25 Jan 2025 04:07:32 +0100 +Subject: package-version: Simplify further. + +* doc/package-version.texi (Propagating the package version): Recommend +use of gl_INIT_PACKAGE_VERSION instead of gl_INIT_PACKAGE. +* build-aux/git-version-gen: Likewise. +* m4/init-package-version.m4: Likewise. +(gl_INIT_PACKAGE_VERSION): Renamed from gl_INIT_PACKAGE. Take only one +argument. Don't fiddle with AC_PACKAGE_NAME, AC_PACKAGE_TARNAME, +PACKAGE. +(gl_RPL_INIT_AUTOMAKE): Update. +--- + ChangeLog | 10 ++++++++++ + build-aux/git-version-gen | 4 ++-- + doc/package-version.texi | 4 ++-- + m4/init-package-version.m4 | 30 ++++++++++++------------------ + 4 files changed, 26 insertions(+), 22 deletions(-) + +--- a/m4/init-package-version.m4 ++++ b/m4/init-package-version.m4 +@@ -1,5 +1,5 @@ + # init-package-version.m4 +-# serial 4 ++# serial 5 + dnl Copyright (C) 1992-2025 Free Software Foundation, Inc. + dnl This file is free software, distributed under the terms of the GNU + dnl General Public License. As a special exception to the GNU General +@@ -60,7 +60,7 @@ dnl the same distribution terms as the r + # AC_INIT(PACKAGE, [dummy], [MORE OPTIONS]) + # AC_CONFIG_SRCDIR(WITNESS) + # . $srcdir/../version.sh +-# gl_INIT_PACKAGE(PACKAGE, $VERSION_NUMBER) ++# gl_INIT_PACKAGE_VERSION($VERSION_NUMBER) + # AM_INIT_AUTOMAKE([OPTIONS]) + # + # and after changing version.sh, the developer can directly configure and build: +@@ -85,32 +85,26 @@ dnl the same distribution terms as the r + # make + # + +-# gl_INIT_PACKAGE(PACKAGE-NAME, VERSION) +-# -------------------------------------- ++# gl_INIT_PACKAGE_VERSION(VERSION) ++# -------------------------------- + # followed by an AM_INIT_AUTOMAKE invocation, + # is like calling AM_INIT_AUTOMAKE(PACKAGE-NAME, VERSION) + # except that it can use computed non-literal arguments. +-AC_DEFUN([gl_INIT_PACKAGE], ++AC_DEFUN([gl_INIT_PACKAGE_VERSION], + [ + AC_BEFORE([$0], [AM_INIT_AUTOMAKE]) + dnl Redefine AM_INIT_AUTOMAKE. + m4_define([gl_AM_INIT_AUTOMAKE], +- m4_bpatsubst(m4_dquote( +- m4_bpatsubst(m4_dquote( +- m4_bpatsubst(m4_dquote( +- m4_defn([AM_INIT_AUTOMAKE])), +- [AC_PACKAGE_NAME], [gl_INIT_DUMMY])), +- [AC_PACKAGE_TARNAME], [gl_INIT_EMPTY])), ++ m4_bpatsubst(m4_dquote(m4_defn([AM_INIT_AUTOMAKE])), + [AC_PACKAGE_VERSION], [gl_INIT_DUMMY]) + [dnl Set variables documented in Automake. +- AC_SUBST([PACKAGE], [$1]) +- AC_SUBST([VERSION], ["$2"]) ++ AC_SUBST([VERSION], ["$1"]) + dnl Set variables documented in Autoconf. +- AC_SUBST([PACKAGE_VERSION], ["$2"]) +- AC_SUBST([PACKAGE_STRING], ["$1 $2"]) +- AC_DEFINE_UNQUOTED([PACKAGE_VERSION], ["$2"], ++ AC_SUBST([PACKAGE_VERSION], ["$1"]) ++ AC_SUBST([PACKAGE_STRING], ["AC_PACKAGE_NAME $1"]) ++ AC_DEFINE_UNQUOTED([PACKAGE_VERSION], ["$1"], + [Define to the version of this package.]) +- AC_DEFINE_UNQUOTED([PACKAGE_STRING], ["$1 $2"], ++ AC_DEFINE_UNQUOTED([PACKAGE_STRING], ["AC_PACKAGE_NAME $1"], + [Define to the full name and version of this package.]) + ]) + m4_define([AM_INIT_AUTOMAKE], +@@ -123,7 +117,7 @@ m4_define([gl_INIT_DUMMY], [gl_INIT_DUMM + m4_define([gl_INIT_DUMMY2], []) + AC_DEFUN([gl_RPL_INIT_AUTOMAKE], [ + m4_ifval([$2], +- [m4_fatal([After gl_INIT_PACKAGE, the two-argument form of AM_INIT_AUTOMAKE cannot be used.])]) ++ [m4_fatal([After gl_INIT_PACKAGE_VERSION, the two-argument form of AM_INIT_AUTOMAKE cannot be used.])]) + gl_AM_INIT_AUTOMAKE([$1 no-define]) + m4_if(m4_index([ $1 ], [ no-define ]), [-1], + [AC_DEFINE_UNQUOTED([PACKAGE], ["$PACKAGE"], [Name of package]) diff --git a/tools/gnulib/patches/653-package-version-warning.patch b/tools/gnulib/patches/653-package-version-warning.patch new file mode 100644 index 0000000000..4baa21ce65 --- /dev/null +++ b/tools/gnulib/patches/653-package-version-warning.patch @@ -0,0 +1,32 @@ +From 2e46209809f751087ca27523283bd5c3e9071d31 Mon Sep 17 00:00:00 2001 +From: Bruno Haible +Date: Sun, 26 Jan 2025 13:26:35 +0100 +Subject: package-version: Avoid compiler warnings in config.log. + +* m4/init-package-version.m4 (gl_INIT_PACKAGE_VERSION): Undefine +PACKAGE_VERSION and PACKAGE_STRING before redefining them. +--- + ChangeLog | 6 ++++++ + m4/init-package-version.m4 | 4 +++- + 2 files changed, 9 insertions(+), 1 deletion(-) + +--- a/m4/init-package-version.m4 ++++ b/m4/init-package-version.m4 +@@ -1,5 +1,5 @@ + # init-package-version.m4 +-# serial 5 ++# serial 6 + dnl Copyright (C) 1992-2025 Free Software Foundation, Inc. + dnl This file is free software, distributed under the terms of the GNU + dnl General Public License. As a special exception to the GNU General +@@ -102,8 +102,10 @@ AC_DEFUN([gl_INIT_PACKAGE_VERSION], + dnl Set variables documented in Autoconf. + AC_SUBST([PACKAGE_VERSION], ["$1"]) + AC_SUBST([PACKAGE_STRING], ["AC_PACKAGE_NAME $1"]) ++ _AC_DEFINE([#undef PACKAGE_VERSION]) + AC_DEFINE_UNQUOTED([PACKAGE_VERSION], ["$1"], + [Define to the version of this package.]) ++ _AC_DEFINE([#undef PACKAGE_STRING]) + AC_DEFINE_UNQUOTED([PACKAGE_STRING], ["AC_PACKAGE_NAME $1"], + [Define to the full name and version of this package.]) + ]) diff --git a/tools/gnulib/patches/660-version-stamp.patch b/tools/gnulib/patches/660-version-stamp.patch new file mode 100644 index 0000000000..d85c0455cf --- /dev/null +++ b/tools/gnulib/patches/660-version-stamp.patch @@ -0,0 +1,75 @@ +From 85599643e2fbf70f7f0bd58831993132ef335705 Mon Sep 17 00:00:00 2001 +From: Bruno Haible +Date: Wed, 22 Jan 2025 21:25:27 +0100 +Subject: New module 'version-stamp'. + +* m4/version-stamp.m4: New file. +* modules/version-stamp: New file. +--- + ChangeLog | 6 ++++++ + m4/version-stamp.m4 | 35 +++++++++++++++++++++++++++++++++++ + modules/version-stamp | 19 +++++++++++++++++++ + 3 files changed, 60 insertions(+) + create mode 100644 m4/version-stamp.m4 + create mode 100644 modules/version-stamp + +--- /dev/null ++++ b/m4/version-stamp.m4 +@@ -0,0 +1,35 @@ ++# version-stamp.m4 ++# serial 1 ++dnl Copyright (C) 2025 Free Software Foundation, Inc. ++dnl This file is free software, distributed under the terms of the GNU ++dnl General Public License. As a special exception to the GNU General ++dnl Public License, this file may be distributed as part of a program ++dnl that contains a configuration script generated by Autoconf, under ++dnl the same distribution terms as the rest of that program. ++ ++# Manages a stamp file, that keeps track when $(VERSION) was last changed. ++# ++# gl_CONFIG_VERSION_STAMP ++# needs to be invoked near the end of the package's top-level configure.ac, ++# before AC_OUTPUT. ++# It makes sure that during the build, ++# - $(top_srcdir)/.version exists, and ++# - when $(VERSION) is changed, $(top_srcdir)/.version gets modified. ++# ++# $(top_srcdir)/.version is a stamp file. Its contents wouldn't matter, ++# except that for detecting the change, we store the value of $(VERSION) ++# in it (but we could just as well store it in a different file). ++AC_DEFUN([gl_CONFIG_VERSION_STAMP], ++[ ++ AC_CONFIG_COMMANDS([version-timestamp], ++ [if test -f "$ac_top_srcdir/.version" \ ++ && test `cat "$ac_top_srcdir/.version"` = "$gl_version"; then ++ # The value of $(VERSION) is the same as last time. ++ : ++ else ++ # The value of $(VERSION) has changed. Update the stamp. ++ echo "$gl_version" > "$ac_top_srcdir/.version" ++ fi ++ ], ++ [gl_version="$VERSION"]) ++]) +--- /dev/null ++++ b/modules/version-stamp +@@ -0,0 +1,19 @@ ++Description: ++Optimized rebuilding of artifacts that depend on $(VERSION). ++ ++Files: ++m4/version-stamp.m4 ++ ++Depends-on: ++ ++configure.ac: ++ ++Makefile.am: ++ ++Include: ++ ++License: ++GPLed build tool ++ ++Maintainer: ++Bruno Haible diff --git a/tools/gnulib/patches/689-vc-mtime.patch b/tools/gnulib/patches/689-vc-mtime.patch new file mode 100644 index 0000000000..62638d7ff4 --- /dev/null +++ b/tools/gnulib/patches/689-vc-mtime.patch @@ -0,0 +1,366 @@ +From 701d20aaf579bb71f35209dd63a272c3d9d21096 Mon Sep 17 00:00:00 2001 +From: Bruno Haible +Date: Mon, 24 Feb 2025 19:03:17 +0100 +Subject: [PATCH] vc-mtime: New module. + +* lib/vc-mtime.h: New file. +* lib/vc-mtime.c: New file. +* modules/vc-mtime: New file. +--- + ChangeLog | 7 ++ + lib/vc-mtime.c | 208 +++++++++++++++++++++++++++++++++++++++++++++++ + lib/vc-mtime.h | 97 ++++++++++++++++++++++ + modules/vc-mtime | 34 ++++++++ + 4 files changed, 346 insertions(+) + create mode 100644 lib/vc-mtime.c + create mode 100644 lib/vc-mtime.h + create mode 100644 modules/vc-mtime + +--- /dev/null ++++ b/lib/vc-mtime.c +@@ -0,0 +1,208 @@ ++/* Return the version-control based modification time of a file. ++ Copyright (C) 2025 Free Software Foundation, Inc. ++ ++ This program is free software: you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation, either version 3 of the License, or ++ (at your option) any later version. ++ ++ This program 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 General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program. If not, see . */ ++ ++/* Written by Bruno Haible , 2025. */ ++ ++#include ++ ++/* Specification. */ ++#include "vc-mtime.h" ++ ++#include ++#include ++ ++#include ++#include "spawn-pipe.h" ++#include "wait-process.h" ++#include "execute.h" ++#include "safe-read.h" ++#include "xstrtol.h" ++#include "stat-time.h" ++#include "gettext.h" ++ ++#define _(msgid) dgettext ("gnulib", msgid) ++ ++ ++/* Determines whether the specified file is under version control. */ ++static bool ++git_vc_controlled (const char *filename) ++{ ++ /* Run "git ls-files FILENAME" and return true if the exit code is 0 ++ and the output is non-empty. */ ++ const char *argv[4]; ++ pid_t child; ++ int fd[1]; ++ ++ argv[0] = "git"; ++ argv[1] = "ls-files"; ++ argv[2] = filename; ++ argv[3] = NULL; ++ child = create_pipe_in ("git", "git", argv, NULL, NULL, ++ DEV_NULL, true, true, false, fd); ++ if (child == -1) ++ return false; ++ ++ /* Read the subprocess output, and test whether it is non-empty. */ ++ size_t count = 0; ++ char c; ++ ++ while (safe_read (fd[0], &c, 1) > 0) ++ count++; ++ ++ close (fd[0]); ++ ++ /* Remove zombie process from process list, and retrieve exit status. */ ++ int exitstatus = ++ wait_subprocess (child, "git", false, true, true, false, NULL); ++ return (exitstatus == 0 && count > 0); ++} ++ ++/* Determines whether the specified file is unmodified, compared to the ++ last version in version control. */ ++static bool ++git_unmodified (const char *filename) ++{ ++ /* Run "git diff --quiet -- HEAD FILENAME" ++ (or "git diff --quiet HEAD FILENAME") ++ and return true if the exit code is 0. ++ The '--' option is for the case that the specified file was removed. */ ++ const char *argv[7]; ++ int exitstatus; ++ ++ argv[0] = "git"; ++ argv[1] = "diff"; ++ argv[2] = "--quiet"; ++ argv[3] = "--"; ++ argv[4] = "HEAD"; ++ argv[5] = filename; ++ argv[6] = NULL; ++ exitstatus = execute ("git", "git", argv, NULL, NULL, ++ false, false, true, true, ++ true, false, NULL); ++ return (exitstatus == 0); ++} ++ ++/* Stores in *MTIME the time of last modification in version control of the ++ specified file, and returns 0. ++ Upon failure, it returns -1. */ ++static int ++git_mtime (struct timespec *mtime, const char *filename) ++{ ++ /* Run "git log -1 --format=%ct -- FILENAME". It prints the time of last ++ modification, as the number of seconds since the Epoch. ++ The '--' option is for the case that the specified file was removed. */ ++ const char *argv[7]; ++ pid_t child; ++ int fd[1]; ++ ++ argv[0] = "git"; ++ argv[1] = "log"; ++ argv[2] = "-1"; ++ argv[3] = "--format=%ct"; ++ argv[4] = "--"; ++ argv[5] = filename; ++ argv[6] = NULL; ++ child = create_pipe_in ("git", "git", argv, NULL, NULL, ++ DEV_NULL, true, true, false, fd); ++ if (child == -1) ++ return -1; ++ ++ /* Retrieve its result. */ ++ FILE *fp; ++ char *line; ++ size_t linesize; ++ size_t linelen; ++ ++ fp = fdopen (fd[0], "r"); ++ if (fp == NULL) ++ error (EXIT_FAILURE, errno, _("fdopen() failed")); ++ ++ line = NULL; linesize = 0; ++ linelen = getline (&line, &linesize, fp); ++ if (linelen == (size_t)(-1)) ++ { ++ error (0, 0, _("%s subprocess I/O error"), "git"); ++ fclose (fp); ++ wait_subprocess (child, "git", true, false, true, false, NULL); ++ } ++ else ++ { ++ int exitstatus; ++ ++ if (linelen > 0 && line[linelen - 1] == '\n') ++ line[linelen - 1] = '\0'; ++ ++ fclose (fp); ++ ++ /* Remove zombie process from process list, and retrieve exit status. */ ++ exitstatus = ++ wait_subprocess (child, "git", true, false, true, false, NULL); ++ if (exitstatus == 0) ++ { ++ char *endptr; ++ unsigned long git_log_time; ++ if (xstrtoul (line, &endptr, 10, &git_log_time, NULL) == LONGINT_OK ++ && endptr == line + strlen (line)) ++ { ++ mtime->tv_sec = git_log_time; ++ mtime->tv_nsec = 0; ++ free (line); ++ return 0; ++ } ++ } ++ } ++ free (line); ++ return -1; ++} ++ ++int ++vc_mtime (struct timespec *mtime, const char *filename) ++{ ++ static bool git_tested; ++ static bool git_present; ++ ++ if (!git_tested) ++ { ++ /* Test for presence of git: ++ "git --version >/dev/null 2>/dev/null" */ ++ const char *argv[3]; ++ int exitstatus; ++ ++ argv[0] = "git"; ++ argv[1] = "--version"; ++ argv[2] = NULL; ++ exitstatus = execute ("git", "git", argv, NULL, NULL, ++ false, false, true, true, ++ true, false, NULL); ++ git_present = (exitstatus == 0); ++ git_tested = true; ++ } ++ ++ if (git_present ++ && git_vc_controlled (filename) ++ && git_unmodified (filename)) ++ { ++ if (git_mtime (mtime, filename) == 0) ++ return 0; ++ } ++ struct stat statbuf; ++ if (stat (filename, &statbuf) == 0) ++ { ++ *mtime = get_stat_mtime (&statbuf); ++ return 0; ++ } ++ return -1; ++} +--- /dev/null ++++ b/lib/vc-mtime.h +@@ -0,0 +1,97 @@ ++/* Return the version-control based modification time of a file. ++ Copyright (C) 2025 Free Software Foundation, Inc. ++ ++ This program is free software: you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation, either version 3 of the License, or ++ (at your option) any later version. ++ ++ This program 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 General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program. If not, see . */ ++ ++/* Written by Bruno Haible , 2025. */ ++ ++#ifndef _VC_MTIME_H ++#define _VC_MTIME_H ++ ++/* Get struct timespec. */ ++#include ++ ++/* The "version-controlled modification time" vc_mtime(F) of a file F ++ is defined as: ++ - If F is under version control and not modified locally: ++ the time of the last change of F in the version control system. ++ - Otherwise: The modification time of F on disk. ++ ++ For now, the only VCS supported by this module is git. (hg and svn are ++ hardly in use any more.) ++ ++ This has the properties that: ++ - Different users who have checked out the same git repo on different ++ machines, at different times, and not done local modifications, ++ get the same vc_mtime(F). ++ - If a user has modified F locally, the modification time of that file ++ counts. ++ - If that user then reverts the modification, they then again get the ++ same vc_mtime(F) as everyone else. ++ - Different users who have unpacked the same tarball (without .git ++ directory) on different machines, at different times, also get the same ++ vc_mtime(F) [but possibly a different one than when the .git directory ++ was present]. (Assuming a POSIX compliant file system.) ++ - When a user commits local modifications into git, this only increases ++ (not decreases) the vc_mtime(F). ++ ++ The purpose of the version-controlled modification time is to produce a ++ reproducible timestamp(Z) of a file Z that depends on files X1, ..., Xn, ++ in such a way that ++ - timestamp(Z) is reproducible, that is, different users on different ++ machines get the same value. ++ - timestamp(Z) is related to reality. It's not just a dummy, like what ++ is suggested in . ++ - One can arrange for timestamp(Z) to respect the modification time ++ relations of a build system. ++ ++ There are two uses of such a timestamp: ++ - It can be set as the modification time of file Z in a file system, or ++ - It can be embedded in Z, with the purpose of telling a user how old ++ the file Z is. For example, in PDF files or in generated documentation, ++ such a time is embedded in a special place. ++ ++ The simplest example is a file Z that depends on files X1, ..., Xn. ++ Generally one will define ++ timestamp(Z) = max (vc_mtime(X1), ..., vc_mtime(Xn)) ++ for an embedded timestamp, or ++ timestamp(Z) = max (vc_mtime(X1), ..., vc_mtime(Xn)) + 1 second ++ for a time stamp in a file system. The added second ++ 1. accounts for fractional seconds in mtime(X1), ..., mtime(Xn), ++ 2. allows for 'make' implementation that attempt to rebuild Z ++ if mtime(Z) == mtime(Xi). ++ ++ A more complicated example is when there are intermediate built files, not ++ under version control. For example, if the build process produces ++ X1, X2 -> Y1 ++ X3, X4 -> Y2 ++ Y1, Y2, X5 -> Z ++ where Y1 and Y2 are intermediate built files, you should ignore the ++ mtime(Y1), mtime(Y2), and consider only the vc_mtime(X1), ..., vc_mtime(X5). ++ */ ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++/* Determines the version-controlled modification time of FILENAME, stores it ++ in *MTIME, and returns 0. ++ Upon failure, it returns -1. */ ++extern int vc_mtime (struct timespec *mtime, const char *filename); ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif /* _VC_MTIME_H */ +--- /dev/null ++++ b/modules/vc-mtime +@@ -0,0 +1,34 @@ ++Description: ++Returns the version-control based modification time of a file. ++ ++Files: ++lib/vc-mtime.h ++lib/vc-mtime.c ++ ++Depends-on: ++time-h ++bool ++spawn-pipe ++wait-process ++execute ++safe-read ++error ++getline ++xstrtol ++stat-time ++gettext-h ++gnulib-i18n ++ ++configure.ac: ++ ++Makefile.am: ++lib_SOURCES += vc-mtime.c ++ ++Include: ++"vm-mtime.h" ++ ++License: ++GPL ++ ++Maintainer: ++Bruno Haible diff --git a/tools/gnulib/patches/755-clean-temp-hashkey.patch b/tools/gnulib/patches/755-clean-temp-hashkey.patch new file mode 100644 index 0000000000..7a4774c9b3 --- /dev/null +++ b/tools/gnulib/patches/755-clean-temp-hashkey.patch @@ -0,0 +1,64 @@ +From f47c5f2e21d0ccedb271b406e35b6963b23a64c4 Mon Sep 17 00:00:00 2001 +From: Bruno Haible +Date: Wed, 30 Apr 2025 13:11:01 +0200 +Subject: [PATCH] clean-temp: Fix link error (regression yesterday). + +* lib/clean-temp.c: Include hashkey-string.h. +(create_temp_dir): Use hashkey_string_* functions instead of +clean_temp_string_*. +* lib/clean-temp-private.h (clean_temp_string_equals, +clean_temp_string_hash): Remove declarations. +* modules/clean-temp (Depends-on): Add hashkey-string. +--- + ChangeLog | 10 ++++++++++ + lib/clean-temp-private.h | 3 --- + lib/clean-temp.c | 5 +++-- + modules/clean-temp | 1 + + 4 files changed, 14 insertions(+), 5 deletions(-) + +--- a/lib/clean-temp-private.h ++++ b/lib/clean-temp-private.h +@@ -68,9 +68,6 @@ struct closeable_fd + #define descriptors clean_temp_descriptors + extern gl_list_t /* */ volatile descriptors; + +-extern bool clean_temp_string_equals (const void *x1, const void *x2); +-extern size_t clean_temp_string_hash (const void *x); +- + extern _GL_ASYNC_SAFE int clean_temp_asyncsafe_close (struct closeable_fd *element); + extern void clean_temp_init_asyncsafe_close (void); + +--- a/lib/clean-temp.c ++++ b/lib/clean-temp.c +@@ -45,6 +45,7 @@ + #include "xmalloca.h" + #include "glthread/lock.h" + #include "thread-optim.h" ++#include "hashkey-string.h" + #include "gl_xlist.h" + #include "gl_linkedhash_list.h" + #include "gl_linked_list.h" +@@ -221,11 +222,11 @@ create_temp_dir (const char *prefix, con + tmpdir->cleanup_verbose = cleanup_verbose; + tmpdir->subdirs = + gl_list_create_empty (GL_LINKEDHASH_LIST, +- clean_temp_string_equals, clean_temp_string_hash, ++ hashkey_string_equals, hashkey_string_hash, + NULL, false); + tmpdir->files = + gl_list_create_empty (GL_LINKEDHASH_LIST, +- clean_temp_string_equals, clean_temp_string_hash, ++ hashkey_string_equals, hashkey_string_hash, + NULL, false); + + /* Create the temporary directory. */ +--- a/modules/clean-temp ++++ b/modules/clean-temp +@@ -24,6 +24,7 @@ rmdir + xalloc + xalloc-die + xmalloca ++hashkey-string + linkedhash-list + linked-list + xlist diff --git a/tools/gnulib/patches/795-string-desc-rename-functions.patch b/tools/gnulib/patches/795-string-desc-rename-functions.patch new file mode 100644 index 0000000000..032545b063 --- /dev/null +++ b/tools/gnulib/patches/795-string-desc-rename-functions.patch @@ -0,0 +1,1137 @@ +From 9a26e7043fa95b4c9ee4576ce8c0ac15668e695e Mon Sep 17 00:00:00 2001 +From: Bruno Haible +Date: Thu, 2 Jan 2025 13:54:54 +0100 +Subject: [PATCH] string-desc, xstring-desc, string-desc-quotearg: Rename + functions. + +* lib/string-desc.h (sd_equals): Renamed from string_desc_equals. +(sd_startswith): Renamed from string_desc_startswith. +(sd_endswith): Renamed from string_desc_endswith. +(sd_cmp): Renamed from string_desc_cmp. +(sd_c_casecmp): Renamed from string_desc_c_casecmp. +(sd_index): Renamed from string_desc_index. +(sd_last_index): Renamed from string_desc_last_index. +(sd_contains): Renamed from string_desc_contains. +(sd_new_empty): Renamed from string_desc_new_empty. +(sd_new_addr): Renamed from string_desc_new_addr. +(sd_from_c): Renamed from string_desc_from_c. +(sd_substring): Renamed from string_desc_substring. +(sd_write): Renamed from string_desc_write. +(sd_fwrite): Renamed from string_desc_fwrite. +(sd_new): Renamed from string_desc_new. +(sd_new_filled): Renamed from string_desc_new_filled. +(sd_copy): Renamed from string_desc_copy. +(sd_concat): Renamed from string_desc_concat. +(sd_c): Renamed from string_desc_c. +(sd_set_char_at): Renamed from string_desc_set_char_at. +(sd_fill): Renamed from string_desc_fill. +(sd_overwrite): Renamed from string_desc_overwrite. +(sd_free): Renamed from string_desc_free. +(sd_length): Renamed from string_desc_length. +(sd_char_at): Renamed from string_desc_char_at. +(sd_data): Renamed from string_desc_data. +(sd_is_empty): Renamed from string_desc_is_empty. +* lib/string-desc.c (sd_equals): Renamed from string_desc_equals. +(sd_startswith): Renamed from string_desc_startswith. +(sd_endswith): Renamed from string_desc_endswith. +(sd_cmp): Renamed from string_desc_cmp. +(sd_c_casecmp): Renamed from string_desc_c_casecmp. +(sd_index): Renamed from string_desc_index. +(sd_last_index): Renamed from string_desc_last_index. +(sd_new_empty): Renamed from string_desc_new_empty. +(sd_new_addr): Renamed from string_desc_new_addr. +(sd_from_c): Renamed from string_desc_from_c. +(sd_substring): Renamed from string_desc_substring. +(sd_write): Renamed from string_desc_write. +(sd_fwrite): Renamed from string_desc_fwrite. +(sd_new): Renamed from string_desc_new. +(sd_new_filled): Renamed from string_desc_new_filled. +(sd_copy): Renamed from string_desc_copy. +(sd_concat): Renamed from string_desc_concat. +(sd_c): Renamed from string_desc_c. +(sd_set_char_at): Renamed from string_desc_set_char_at. +(sd_fill): Renamed from string_desc_fill. +(sd_overwrite): Renamed from string_desc_overwrite. +(sd_free): Renamed from string_desc_free. +* lib/xstring-desc.h (xsd_concat): Renamed from xstring_desc_concat. +(xsd_new): Renamed from xstring_desc_new. +(xsd_new_filled): Renamed from xstring_desc_new_filled. +(xsd_copy): Renamed from xstring_desc_copy. +(xsd_c): Renamed from xstring_desc_c. +* lib/xstring-desc.c (xsd_concat): Renamed from xstring_desc_concat. +* lib/string-desc-quotearg.h (sd_quotearg_buffer): Renamed from +string_desc_quotearg_buffer. +(sd_quotearg_alloc): Renamed from string_desc_quotearg_alloc. +(sd_quotearg_n): Renamed from string_desc_quotearg_n. +(sd_quotearg): Renamed from string_desc_quotearg. +(sd_quotearg_n_style): Renamed from string_desc_quotearg_n_style. +(sd_quotearg_style): Renamed from string_desc_quotearg_style. +(sd_quotearg_char): Renamed from string_desc_quotearg_char. +(sd_quotearg_colon): Renamed from string_desc_quotearg_colon. +(sd_quotearg_n_custom): Renamed from string_desc_quotearg_n_custom. +(sd_quotearg_custom): Renamed from sd_quotearg_n_custom. +* lib/string-desc-contains.c (sd_contains): Renamed from +string_desc_contains. +* lib/string-buffer.h: Update. +* lib/string-buffer.c (sb_append_desc, sb_contents, sb_dupfree): Update. +* lib/xstring-buffer.c (sb_xdupfree): Update. +* lib/sf-istream.c (sf_istream_init_from_string_desc): Update. +* tests/test-string-desc.c (main): Update. +* tests/test-string-desc.sh: Update. +* tests/test-xstring-desc.c (main): Update. +* tests/test-string-desc-quotearg.c (main): Update. +* tests/test-string-buffer.c (main): Update. +* tests/test-sf-istream.c (main): Update. +* tests/test-sfl-istream.c (main): Update. +* doc/string-desc.texi: Update. +* doc/strings.texi: Update. +* NEWS: Mention the change. +--- + ChangeLog | 86 +++++++++ + NEWS | 4 + + doc/string-desc.texi | 4 +- + doc/strings.texi | 18 +- + lib/sf-istream.c | 4 +- + lib/string-buffer.c | 12 +- + lib/string-buffer.h | 4 +- + lib/string-desc-contains.c | 2 +- + lib/string-desc-quotearg.h | 114 ++++++------ + lib/string-desc.c | 52 +++--- + lib/string-desc.h | 62 +++---- + lib/xstring-buffer.c | 4 +- + lib/xstring-desc.c | 2 +- + lib/xstring-desc.h | 26 +-- + tests/test-sf-istream.c | 4 +- + tests/test-sfl-istream.c | 4 +- + tests/test-string-buffer.c | 18 +- + tests/test-string-desc-quotearg.c | 44 ++--- + tests/test-string-desc.c | 296 +++++++++++++++--------------- + tests/test-string-desc.sh | 4 +- + tests/test-xstring-desc.c | 68 +++---- + 21 files changed, 461 insertions(+), 371 deletions(-) + +--- a/lib/sf-istream.c ++++ b/lib/sf-istream.c +@@ -46,8 +46,8 @@ sf_istream_init_from_string_desc (sf_ist + string_desc_t input) + { + stream->fp = NULL; +- stream->input = string_desc_data (input); +- stream->input_end = stream->input + string_desc_length (input); ++ stream->input = sd_data (input); ++ stream->input_end = stream->input + sd_length (input); + } + + int +--- a/lib/string-buffer.c ++++ b/lib/string-buffer.c +@@ -101,13 +101,13 @@ sb_append1 (struct string_buffer *buffer + int + sb_append_desc (struct string_buffer *buffer, string_desc_t s) + { +- size_t len = string_desc_length (s); ++ size_t len = sd_length (s); + if (sb_ensure_more_bytes (buffer, len) < 0) + { + buffer->error = true; + return -1; + } +- memcpy (buffer->data + buffer->length, string_desc_data (s), len); ++ memcpy (buffer->data + buffer->length, sd_data (s), len); + buffer->length += len; + return 0; + } +@@ -136,7 +136,7 @@ sb_free (struct string_buffer *buffer) + string_desc_t + sb_contents (struct string_buffer *buffer) + { +- return string_desc_new_addr (buffer->length, buffer->data); ++ return sd_new_addr (buffer->length, buffer->data); + } + + const char * +@@ -162,7 +162,7 @@ sb_dupfree (struct string_buffer *buffer + if (copy == NULL) + goto fail; + memcpy (copy, buffer->data, length); +- return string_desc_new_addr (length, copy); ++ return sd_new_addr (length, copy); + } + else + { +@@ -174,12 +174,12 @@ sb_dupfree (struct string_buffer *buffer + if (contents == NULL) + goto fail; + } +- return string_desc_new_addr (length, contents); ++ return sd_new_addr (length, contents); + } + + fail: + sb_free (buffer); +- return string_desc_new_addr (0, NULL); ++ return sd_new_addr (0, NULL); + } + + char * +--- a/lib/string-buffer.h ++++ b/lib/string-buffer.h +@@ -115,7 +115,7 @@ extern const char * sb_contents_c (struc + + /* Returns the contents of BUFFER and frees all other memory held by BUFFER. + Returns NULL upon failure or if there was an error earlier. +- It is the responsibility of the caller to string_desc_free() the result. */ ++ It is the responsibility of the caller to sd_free() the result. */ + extern string_desc_t sb_dupfree (struct string_buffer *buffer) + _GL_ATTRIBUTE_RELEASE_CAPABILITY (buffer->data); + +@@ -182,7 +182,7 @@ extern const char * sb_xcontents_c (stru + + /* Returns the contents of BUFFER and frees all other memory held by BUFFER. + Returns (0, NULL) if there was an error earlier. +- It is the responsibility of the caller to string_desc_free() the result. */ ++ It is the responsibility of the caller to sd_free() the result. */ + extern string_desc_t sb_xdupfree (struct string_buffer *buffer) + _GL_ATTRIBUTE_RELEASE_CAPABILITY (buffer->data); + +--- a/lib/string-desc-contains.c ++++ b/lib/string-desc-contains.c +@@ -31,7 +31,7 @@ + which — depending on platforms — costs up to 2 KB of binary code. */ + + ptrdiff_t +-string_desc_contains (string_desc_t haystack, string_desc_t needle) ++sd_contains (string_desc_t haystack, string_desc_t needle) + { + if (needle._nbytes == 0) + return 0; +--- a/lib/string-desc-quotearg.h ++++ b/lib/string-desc-quotearg.h +@@ -50,22 +50,22 @@ extern "C" { + does not use backslash escapes and the flags of O do not request + elision of null bytes. */ + #if 0 +-extern size_t string_desc_quotearg_buffer (char *restrict buffer, +- size_t buffersize, +- string_desc_t arg, +- struct quoting_options const *o); ++extern size_t sd_quotearg_buffer (char *restrict buffer, ++ size_t buffersize, ++ string_desc_t arg, ++ struct quoting_options const *o); + #endif + +-/* Like string_desc_quotearg_buffer, except return the result in a newly ++/* Like sd_quotearg_buffer, except return the result in a newly + allocated buffer and store its length, excluding the terminating null + byte, in *SIZE. It is the caller's responsibility to free the result. + The result might contain embedded null bytes if the style of O does + not use backslash escapes and the flags of O do not request elision + of null bytes. */ + #if 0 +-extern char *string_desc_quotearg_alloc (string_desc_t arg, +- size_t *size, +- struct quoting_options const *o) ++extern char *sd_quotearg_alloc (string_desc_t arg, ++ size_t *size, ++ struct quoting_options const *o) + _GL_ATTRIBUTE_NONNULL ((2)) + _GL_ATTRIBUTE_MALLOC _GL_ATTRIBUTE_DEALLOC_FREE + _GL_ATTRIBUTE_RETURNS_NONNULL; +@@ -77,68 +77,68 @@ extern char *string_desc_quotearg_alloc + reused by the next call to this function with the same value of N. + N must be nonnegative. */ + #if 0 +-extern char *string_desc_quotearg_n (int n, string_desc_t arg); ++extern char *sd_quotearg_n (int n, string_desc_t arg); + #endif + +-/* Equivalent to string_desc_quotearg_n (0, ARG). */ ++/* Equivalent to sd_quotearg_n (0, ARG). */ + #if 0 +-extern char *string_desc_quotearg (string_desc_t arg); ++extern char *sd_quotearg (string_desc_t arg); + #endif + + /* Use style S and storage slot N to return a quoted version of the string ARG. +- This is like string_desc_quotearg_n (N, ARG), except that it uses S ++ This is like sd_quotearg_n (N, ARG), except that it uses S + with no other options to specify the quoting method. */ + #if 0 +-extern char *string_desc_quotearg_n_style (int n, enum quoting_style s, +- string_desc_t arg); ++extern char *sd_quotearg_n_style (int n, enum quoting_style s, ++ string_desc_t arg); + #endif + +-/* Equivalent to string_desc_quotearg_n_style (0, S, ARG). */ ++/* Equivalent to sd_quotearg_n_style (0, S, ARG). */ + #if 0 +-extern char *string_desc_quotearg_style (enum quoting_style s, +- string_desc_t arg); ++extern char *sd_quotearg_style (enum quoting_style s, ++ string_desc_t arg); + #endif + +-/* Like string_desc_quotearg (ARG), except also quote any instances of CH. ++/* Like sd_quotearg (ARG), except also quote any instances of CH. + See set_char_quoting for a description of acceptable CH values. */ + #if 0 +-extern char *string_desc_quotearg_char (string_desc_t arg, char ch); ++extern char *sd_quotearg_char (string_desc_t arg, char ch); + #endif + +-/* Equivalent to string_desc_quotearg_char (ARG, ':'). */ ++/* Equivalent to sd_quotearg_char (ARG, ':'). */ + #if 0 +-extern char *string_desc_quotearg_colon (string_desc_t arg); ++extern char *sd_quotearg_colon (string_desc_t arg); + #endif + +-/* Like string_desc_quotearg_n_style (N, S, ARG) but with S as ++/* Like sd_quotearg_n_style (N, S, ARG) but with S as + custom_quoting_style with left quote as LEFT_QUOTE and right quote + as RIGHT_QUOTE. See set_custom_quoting for a description of acceptable + LEFT_QUOTE and RIGHT_QUOTE values. */ + #if 0 +-extern char *string_desc_quotearg_n_custom (int n, +- char const *left_quote, +- char const *right_quote, +- string_desc_t arg); ++extern char *sd_quotearg_n_custom (int n, ++ char const *left_quote, ++ char const *right_quote, ++ string_desc_t arg); + #endif + + /* Equivalent to +- string_desc_quotearg_n_custom (0, LEFT_QUOTE, RIGHT_QUOTE, ARG). */ ++ sd_quotearg_n_custom (0, LEFT_QUOTE, RIGHT_QUOTE, ARG). */ + #if 0 +-extern char *string_desc_quotearg_custom (char const *left_quote, +- char const *right_quote, +- string_desc_t arg); ++extern char *sd_quotearg_custom (char const *left_quote, ++ char const *right_quote, ++ string_desc_t arg); + #endif + + + /* ==== Inline function definitions ==== */ + + GL_STRING_DESC_QUOTEARG_INLINE size_t +-string_desc_quotearg_buffer (char *restrict buffer, size_t buffersize, +- string_desc_t arg, +- struct quoting_options const *o) ++sd_quotearg_buffer (char *restrict buffer, size_t buffersize, ++ string_desc_t arg, ++ struct quoting_options const *o) + { + return quotearg_buffer (buffer, buffersize, +- string_desc_data (arg), string_desc_length (arg), ++ sd_data (arg), sd_length (arg), + o); + } + +@@ -147,69 +147,69 @@ _GL_ATTRIBUTE_NONNULL ((2)) + _GL_ATTRIBUTE_MALLOC _GL_ATTRIBUTE_DEALLOC_FREE + _GL_ATTRIBUTE_RETURNS_NONNULL + char * +-string_desc_quotearg_alloc (string_desc_t arg, +- size_t *size, +- struct quoting_options const *o) ++sd_quotearg_alloc (string_desc_t arg, ++ size_t *size, ++ struct quoting_options const *o) + { +- return quotearg_alloc_mem (string_desc_data (arg), string_desc_length (arg), ++ return quotearg_alloc_mem (sd_data (arg), sd_length (arg), + size, + o); + } + + GL_STRING_DESC_QUOTEARG_INLINE char * +-string_desc_quotearg_n (int n, string_desc_t arg) ++sd_quotearg_n (int n, string_desc_t arg) + { +- return quotearg_n_mem (n, string_desc_data (arg), string_desc_length (arg)); ++ return quotearg_n_mem (n, sd_data (arg), sd_length (arg)); + } + + GL_STRING_DESC_QUOTEARG_INLINE char * +-string_desc_quotearg (string_desc_t arg) ++sd_quotearg (string_desc_t arg) + { +- return quotearg_mem (string_desc_data (arg), string_desc_length (arg)); ++ return quotearg_mem (sd_data (arg), sd_length (arg)); + } + + GL_STRING_DESC_QUOTEARG_INLINE char * +-string_desc_quotearg_n_style (int n, enum quoting_style s, string_desc_t arg) ++sd_quotearg_n_style (int n, enum quoting_style s, string_desc_t arg) + { + return quotearg_n_style_mem (n, s, +- string_desc_data (arg), string_desc_length (arg)); ++ sd_data (arg), sd_length (arg)); + } + + GL_STRING_DESC_QUOTEARG_INLINE char * +-string_desc_quotearg_style (enum quoting_style s, string_desc_t arg) ++sd_quotearg_style (enum quoting_style s, string_desc_t arg) + { + return quotearg_style_mem (s, +- string_desc_data (arg), string_desc_length (arg)); ++ sd_data (arg), sd_length (arg)); + } + + GL_STRING_DESC_QUOTEARG_INLINE char * +-string_desc_quotearg_char (string_desc_t arg, char ch) ++sd_quotearg_char (string_desc_t arg, char ch) + { +- return quotearg_char_mem (string_desc_data (arg), string_desc_length (arg), ++ return quotearg_char_mem (sd_data (arg), sd_length (arg), + ch); + } + + GL_STRING_DESC_QUOTEARG_INLINE char * +-string_desc_quotearg_colon (string_desc_t arg) ++sd_quotearg_colon (string_desc_t arg) + { +- return quotearg_colon_mem (string_desc_data (arg), string_desc_length (arg)); ++ return quotearg_colon_mem (sd_data (arg), sd_length (arg)); + } + + GL_STRING_DESC_QUOTEARG_INLINE char * +-string_desc_quotearg_n_custom (int n, +- char const *left_quote, char const *right_quote, +- string_desc_t arg) ++sd_quotearg_n_custom (int n, ++ char const *left_quote, char const *right_quote, ++ string_desc_t arg) + { + return quotearg_n_custom_mem (n, left_quote, right_quote, +- string_desc_data (arg), string_desc_length (arg)); ++ sd_data (arg), sd_length (arg)); + } + + GL_STRING_DESC_QUOTEARG_INLINE char * +-string_desc_quotearg_custom (char const *left_quote, char const *right_quote, +- string_desc_t arg) ++sd_quotearg_custom (char const *left_quote, char const *right_quote, ++ string_desc_t arg) + { + return quotearg_custom_mem (left_quote, right_quote, +- string_desc_data (arg), string_desc_length (arg)); ++ sd_data (arg), sd_length (arg)); + } + + +--- a/lib/string-desc.c ++++ b/lib/string-desc.c +@@ -39,14 +39,14 @@ + + /* Return true if A and B are equal. */ + bool +-string_desc_equals (string_desc_t a, string_desc_t b) ++sd_equals (string_desc_t a, string_desc_t b) + { + return (a._nbytes == b._nbytes + && (a._nbytes == 0 || memcmp (a._data, b._data, a._nbytes) == 0)); + } + + bool +-string_desc_startswith (string_desc_t s, string_desc_t prefix) ++sd_startswith (string_desc_t s, string_desc_t prefix) + { + return (s._nbytes >= prefix._nbytes + && (prefix._nbytes == 0 +@@ -54,7 +54,7 @@ string_desc_startswith (string_desc_t s, + } + + bool +-string_desc_endswith (string_desc_t s, string_desc_t suffix) ++sd_endswith (string_desc_t s, string_desc_t suffix) + { + return (s._nbytes >= suffix._nbytes + && (suffix._nbytes == 0 +@@ -63,7 +63,7 @@ string_desc_endswith (string_desc_t s, s + } + + int +-string_desc_cmp (string_desc_t a, string_desc_t b) ++sd_cmp (string_desc_t a, string_desc_t b) + { + if (a._nbytes > b._nbytes) + { +@@ -86,14 +86,14 @@ string_desc_cmp (string_desc_t a, string + } + + int +-string_desc_c_casecmp (string_desc_t a, string_desc_t b) ++sd_c_casecmp (string_desc_t a, string_desc_t b) + { + /* Don't use memcasecmp here, since it uses the current locale, not the + "C" locale. */ +- idx_t an = string_desc_length (a); +- idx_t bn = string_desc_length (b); +- const char *ap = string_desc_data (a); +- const char *bp = string_desc_data (b); ++ idx_t an = sd_length (a); ++ idx_t bn = sd_length (b); ++ const char *ap = sd_data (a); ++ const char *bp = sd_data (b); + idx_t n = (an < bn ? an : bn); + idx_t i; + for (i = 0; i < n; i++) +@@ -108,7 +108,7 @@ string_desc_c_casecmp (string_desc_t a, + } + + ptrdiff_t +-string_desc_index (string_desc_t s, char c) ++sd_index (string_desc_t s, char c) + { + if (s._nbytes > 0) + { +@@ -120,7 +120,7 @@ string_desc_index (string_desc_t s, char + } + + ptrdiff_t +-string_desc_last_index (string_desc_t s, char c) ++sd_last_index (string_desc_t s, char c) + { + if (s._nbytes > 0) + { +@@ -132,7 +132,7 @@ string_desc_last_index (string_desc_t s, + } + + string_desc_t +-string_desc_new_empty (void) ++sd_new_empty (void) + { + string_desc_t result; + +@@ -144,7 +144,7 @@ string_desc_new_empty (void) + } + + string_desc_t +-string_desc_new_addr (idx_t n, char *addr) ++sd_new_addr (idx_t n, char *addr) + { + string_desc_t result; + +@@ -158,7 +158,7 @@ string_desc_new_addr (idx_t n, char *add + } + + string_desc_t +-string_desc_from_c (const char *s) ++sd_from_c (const char *s) + { + string_desc_t result; + +@@ -169,7 +169,7 @@ string_desc_from_c (const char *s) + } + + string_desc_t +-string_desc_substring (string_desc_t s, idx_t start, idx_t end) ++sd_substring (string_desc_t s, idx_t start, idx_t end) + { + string_desc_t result; + +@@ -184,7 +184,7 @@ string_desc_substring (string_desc_t s, + } + + int +-string_desc_write (int fd, string_desc_t s) ++sd_write (int fd, string_desc_t s) + { + if (s._nbytes > 0) + if (full_write (fd, s._data, s._nbytes) != s._nbytes) +@@ -194,7 +194,7 @@ string_desc_write (int fd, string_desc_t + } + + int +-string_desc_fwrite (FILE *fp, string_desc_t s) ++sd_fwrite (FILE *fp, string_desc_t s) + { + if (s._nbytes > 0) + if (fwrite (s._data, 1, s._nbytes, fp) != s._nbytes) +@@ -206,7 +206,7 @@ string_desc_fwrite (FILE *fp, string_des + /* ==== Memory-allocating operations on string descriptors ==== */ + + int +-string_desc_new (string_desc_t *resultp, idx_t n) ++sd_new (string_desc_t *resultp, idx_t n) + { + string_desc_t result; + +@@ -230,7 +230,7 @@ string_desc_new (string_desc_t *resultp, + } + + int +-string_desc_new_filled (string_desc_t *resultp, idx_t n, char c) ++sd_new_filled (string_desc_t *resultp, idx_t n, char c) + { + string_desc_t result; + +@@ -251,7 +251,7 @@ string_desc_new_filled (string_desc_t *r + } + + int +-string_desc_copy (string_desc_t *resultp, string_desc_t s) ++sd_copy (string_desc_t *resultp, string_desc_t s) + { + string_desc_t result; + idx_t n = s._nbytes; +@@ -273,7 +273,7 @@ string_desc_copy (string_desc_t *resultp + } + + int +-string_desc_concat (string_desc_t *resultp, idx_t n, string_desc_t string1, ...) ++sd_concat (string_desc_t *resultp, idx_t n, string_desc_t string1, ...) + { + if (n <= 0) + /* Invalid argument. */ +@@ -327,7 +327,7 @@ string_desc_concat (string_desc_t *resul + } + + char * +-string_desc_c (string_desc_t s) ++sd_c (string_desc_t s) + { + idx_t n = s._nbytes; + char *result = (char *) imalloc (n + 1); +@@ -345,7 +345,7 @@ string_desc_c (string_desc_t s) + /* ==== Operations with side effects on string descriptors ==== */ + + void +-string_desc_set_char_at (string_desc_t s, idx_t i, char c) ++sd_set_char_at (string_desc_t s, idx_t i, char c) + { + if (!(i >= 0 && i < s._nbytes)) + /* Invalid argument. */ +@@ -354,7 +354,7 @@ string_desc_set_char_at (string_desc_t s + } + + void +-string_desc_fill (string_desc_t s, idx_t start, idx_t end, char c) ++sd_fill (string_desc_t s, idx_t start, idx_t end, char c) + { + if (!(start >= 0 && start <= end)) + /* Invalid arguments. */ +@@ -365,7 +365,7 @@ string_desc_fill (string_desc_t s, idx_t + } + + void +-string_desc_overwrite (string_desc_t s, idx_t start, string_desc_t t) ++sd_overwrite (string_desc_t s, idx_t start, string_desc_t t) + { + if (!(start >= 0 && start + t._nbytes <= s._nbytes)) + /* Invalid arguments. */ +@@ -376,7 +376,7 @@ string_desc_overwrite (string_desc_t s, + } + + void +-string_desc_free (string_desc_t s) ++sd_free (string_desc_t s) + { + free (s._data); + } +--- a/lib/string-desc.h ++++ b/lib/string-desc.h +@@ -69,82 +69,82 @@ struct string_desc_t + + /* Return the length of the string S. */ + #if 0 /* Defined inline below. */ +-extern idx_t string_desc_length (string_desc_t s); ++extern idx_t sd_length (string_desc_t s); + #endif + + /* Return the byte at index I of string S. + I must be < length(S). */ + #if 0 /* Defined inline below. */ +-extern char string_desc_char_at (string_desc_t s, idx_t i); ++extern char sd_char_at (string_desc_t s, idx_t i); + #endif + + /* Return a read-only view of the bytes of S. */ + #if 0 /* Defined inline below. */ +-extern const char * string_desc_data (string_desc_t s); ++extern const char * sd_data (string_desc_t s); + #endif + + /* Return true if S is the empty string. */ + #if 0 /* Defined inline below. */ +-extern bool string_desc_is_empty (string_desc_t s); ++extern bool sd_is_empty (string_desc_t s); + #endif + + /* Return true if A and B are equal. */ +-extern bool string_desc_equals (string_desc_t a, string_desc_t b); ++extern bool sd_equals (string_desc_t a, string_desc_t b); + + /* Return true if S starts with PREFIX. */ +-extern bool string_desc_startswith (string_desc_t s, string_desc_t prefix); ++extern bool sd_startswith (string_desc_t s, string_desc_t prefix); + + /* Return true if S ends with SUFFIX. */ +-extern bool string_desc_endswith (string_desc_t s, string_desc_t suffix); ++extern bool sd_endswith (string_desc_t s, string_desc_t suffix); + + /* Return > 0, == 0, or < 0 if A > B, A == B, A < B. + This uses a lexicographic ordering, where the bytes are compared as + 'unsigned char'. */ +-extern int string_desc_cmp (string_desc_t a, string_desc_t b); ++extern int sd_cmp (string_desc_t a, string_desc_t b); + + /* Return > 0, == 0, or < 0 if A > B, A == B, A < B. + Either A or B must be entirely ASCII. + This uses a lexicographic ordering, where the bytes are compared as + 'unsigned char', ignoring case, in the "C" locale. */ +-extern int string_desc_c_casecmp (string_desc_t a, string_desc_t b); ++extern int sd_c_casecmp (string_desc_t a, string_desc_t b); + + /* Return the index of the first occurrence of C in S, + or -1 if there is none. */ +-extern ptrdiff_t string_desc_index (string_desc_t s, char c); ++extern ptrdiff_t sd_index (string_desc_t s, char c); + + /* Return the index of the last occurrence of C in S, + or -1 if there is none. */ +-extern ptrdiff_t string_desc_last_index (string_desc_t s, char c); ++extern ptrdiff_t sd_last_index (string_desc_t s, char c); + + /* Return the index of the first occurrence of NEEDLE in HAYSTACK, + or -1 if there is none. */ +-extern ptrdiff_t string_desc_contains (string_desc_t haystack, string_desc_t needle); ++extern ptrdiff_t sd_contains (string_desc_t haystack, string_desc_t needle); + + /* Return an empty string. */ +-extern string_desc_t string_desc_new_empty (void); ++extern string_desc_t sd_new_empty (void); + + /* Construct and return a string of length N, at the given memory address. */ +-extern string_desc_t string_desc_new_addr (idx_t n, char *addr); ++extern string_desc_t sd_new_addr (idx_t n, char *addr); + + /* Return a string that represents the C string S, of length strlen (S). */ +-extern string_desc_t string_desc_from_c (const char *s); ++extern string_desc_t sd_from_c (const char *s); + + /* Return the substring of S, starting at offset START and ending at offset END. + START must be <= END. + The result is of length END - START. + The result must not be freed (since its storage is part of the storage + of S). */ +-extern string_desc_t string_desc_substring (string_desc_t s, idx_t start, idx_t end); ++extern string_desc_t sd_substring (string_desc_t s, idx_t start, idx_t end); + + /* Output S to the file descriptor FD. + Return 0 if successful. + Upon error, return -1 with errno set. */ +-extern int string_desc_write (int fd, string_desc_t s); ++extern int sd_write (int fd, string_desc_t s); + + /* Output S to the FILE stream FP. + Return 0 if successful. + Upon error, return -1. */ +-extern int string_desc_fwrite (FILE *fp, string_desc_t s); ++extern int sd_fwrite (FILE *fp, string_desc_t s); + + + /* ==== Memory-allocating operations on string descriptors ==== */ +@@ -153,61 +153,61 @@ extern int string_desc_fwrite (FILE *fp, + Return 0 if successful. + Upon error, return -1 with errno set. */ + _GL_ATTRIBUTE_NODISCARD +-extern int string_desc_new (string_desc_t *resultp, idx_t n); ++extern int sd_new (string_desc_t *resultp, idx_t n); + + /* Construct a string of length N, filled with C. + Return 0 if successful. + Upon error, return -1 with errno set. */ + _GL_ATTRIBUTE_NODISCARD +-extern int string_desc_new_filled (string_desc_t *resultp, idx_t n, char c); ++extern int sd_new_filled (string_desc_t *resultp, idx_t n, char c); + + /* Construct a copy of string S. + Return 0 if successful. + Upon error, return -1 with errno set. */ + _GL_ATTRIBUTE_NODISCARD +-extern int string_desc_copy (string_desc_t *resultp, string_desc_t s); ++extern int sd_copy (string_desc_t *resultp, string_desc_t s); + + /* Construct the concatenation of N strings. N must be > 0. + Return 0 if successful. + Upon error, return -1 with errno set. */ + _GL_ATTRIBUTE_NODISCARD +-extern int string_desc_concat (string_desc_t *resultp, idx_t n, string_desc_t string1, ...); ++extern int sd_concat (string_desc_t *resultp, idx_t n, string_desc_t string1, ...); + + /* Construct a copy of string S, as a NUL-terminated C string. + Return it is successful. + Upon error, return NULL with errno set. */ +-extern char * string_desc_c (string_desc_t s) _GL_ATTRIBUTE_DEALLOC_FREE; ++extern char * sd_c (string_desc_t s) _GL_ATTRIBUTE_DEALLOC_FREE; + + + /* ==== Operations with side effects on string descriptors ==== */ + + /* Overwrite the byte at index I of string S with C. + I must be < length(S). */ +-extern void string_desc_set_char_at (string_desc_t s, idx_t i, char c); ++extern void sd_set_char_at (string_desc_t s, idx_t i, char c); + + /* Fill part of S, starting at offset START and ending at offset END, + with copies of C. + START must be <= END. */ +-extern void string_desc_fill (string_desc_t s, idx_t start, idx_t end, char c); ++extern void sd_fill (string_desc_t s, idx_t start, idx_t end, char c); + + /* Overwrite part of S with T, starting at offset START. + START + length(T) must be <= length (S). */ +-extern void string_desc_overwrite (string_desc_t s, idx_t start, string_desc_t t); ++extern void sd_overwrite (string_desc_t s, idx_t start, string_desc_t t); + + /* Free S. */ +-extern void string_desc_free (string_desc_t s); ++extern void sd_free (string_desc_t s); + + + /* ==== Inline function definitions ==== */ + + GL_STRING_DESC_INLINE idx_t +-string_desc_length (string_desc_t s) ++sd_length (string_desc_t s) + { + return s._nbytes; + } + + GL_STRING_DESC_INLINE char +-string_desc_char_at (string_desc_t s, idx_t i) ++sd_char_at (string_desc_t s, idx_t i) + { + if (!(i >= 0 && i < s._nbytes)) + /* Invalid argument. */ +@@ -216,13 +216,13 @@ string_desc_char_at (string_desc_t s, id + } + + GL_STRING_DESC_INLINE const char * +-string_desc_data (string_desc_t s) ++sd_data (string_desc_t s) + { + return s._data; + } + + GL_STRING_DESC_INLINE bool +-string_desc_is_empty (string_desc_t s) ++sd_is_empty (string_desc_t s) + { + return s._nbytes == 0; + } +--- a/lib/xstring-buffer.c ++++ b/lib/xstring-buffer.c +@@ -59,10 +59,10 @@ sb_xdupfree (struct string_buffer *buffe + if (buffer->error) + { + sb_free (buffer); +- return string_desc_new_addr (0, NULL); ++ return sd_new_addr (0, NULL); + } + string_desc_t contents = sb_dupfree (buffer); +- if (string_desc_data (contents) == NULL) ++ if (sd_data (contents) == NULL) + xalloc_die (); + return contents; + } +--- a/lib/xstring-desc.c ++++ b/lib/xstring-desc.c +@@ -22,7 +22,7 @@ + #include "ialloc.h" + + string_desc_t +-xstring_desc_concat (idx_t n, string_desc_t string1, ...) ++xsd_concat (idx_t n, string_desc_t string1, ...) + { + if (n <= 0) + /* Invalid argument. */ +--- a/lib/xstring-desc.h ++++ b/lib/xstring-desc.h +@@ -43,53 +43,53 @@ extern "C" { + + /* Return a string of length N, with uninitialized contents. */ + #if 0 /* Defined inline below. */ +-extern string_desc_t xstring_desc_new (idx_t n); ++extern string_desc_t xsd_new (idx_t n); + #endif + + /* Return a string of length N, filled with C. */ + #if 0 /* Defined inline below. */ +-extern string_desc_t xstring_desc_new_filled (idx_t n, char c); ++extern string_desc_t xsd_new_filled (idx_t n, char c); + #endif + + /* Return a copy of string S. */ + #if 0 /* Defined inline below. */ +-extern string_desc_t xstring_desc_copy (string_desc_t s); ++extern string_desc_t xsd_copy (string_desc_t s); + #endif + + /* Return the concatenation of N strings. N must be > 0. */ +-extern string_desc_t xstring_desc_concat (idx_t n, string_desc_t string1, ...); ++extern string_desc_t xsd_concat (idx_t n, string_desc_t string1, ...); + + /* Construct and return a copy of string S, as a NUL-terminated C string. */ + #if 0 /* Defined inline below. */ +-extern char * xstring_desc_c (string_desc_t s) _GL_ATTRIBUTE_DEALLOC_FREE; ++extern char * xsd_c (string_desc_t s) _GL_ATTRIBUTE_DEALLOC_FREE; + #endif + + + /* ==== Inline function definitions ==== */ + + GL_XSTRING_DESC_INLINE string_desc_t +-xstring_desc_new (idx_t n) ++xsd_new (idx_t n) + { + string_desc_t result; +- if (string_desc_new (&result, n) < 0) ++ if (sd_new (&result, n) < 0) + xalloc_die (); + return result; + } + + GL_XSTRING_DESC_INLINE string_desc_t +-xstring_desc_new_filled (idx_t n, char c) ++xsd_new_filled (idx_t n, char c) + { + string_desc_t result; +- if (string_desc_new_filled (&result, n, c) < 0) ++ if (sd_new_filled (&result, n, c) < 0) + xalloc_die (); + return result; + } + + GL_XSTRING_DESC_INLINE string_desc_t +-xstring_desc_copy (string_desc_t s) ++xsd_copy (string_desc_t s) + { + string_desc_t result; +- if (string_desc_copy (&result, s) < 0) ++ if (sd_copy (&result, s) < 0) + xalloc_die (); + return result; + } +@@ -97,9 +97,9 @@ xstring_desc_copy (string_desc_t s) + GL_XSTRING_DESC_INLINE + _GL_ATTRIBUTE_DEALLOC_FREE + char * +-xstring_desc_c (string_desc_t s) ++xsd_c (string_desc_t s) + { +- char *result = string_desc_c (s); ++ char *result = sd_c (s); + if (result == NULL) + xalloc_die (); + return result; +--- a/tests/test-string-desc-quotearg.c ++++ b/tests/test-string-desc-quotearg.c +@@ -28,75 +28,75 @@ + int + main (void) + { +- string_desc_t s1 = string_desc_from_c ("Hello world!"); +- string_desc_t s2 = string_desc_new_addr (21, "The\0quick\0brown\0\0fox"); ++ string_desc_t s1 = sd_from_c ("Hello world!"); ++ string_desc_t s2 = sd_new_addr (21, "The\0quick\0brown\0\0fox"); + +- /* Test string_desc_quotearg_buffer. */ ++ /* Test sd_quotearg_buffer. */ + { + char buf[80]; +- size_t n = string_desc_quotearg_buffer (buf, sizeof (buf), s2, NULL); ++ size_t n = sd_quotearg_buffer (buf, sizeof (buf), s2, NULL); + ASSERT (n == 21); + ASSERT (memcmp (buf, "The\0quick\0brown\0\0fox", n) == 0); + } + +- /* Test string_desc_quotearg_alloc. */ ++ /* Test sd_quotearg_alloc. */ + { + size_t n; +- char *ret = string_desc_quotearg_alloc (s2, &n, NULL); ++ char *ret = sd_quotearg_alloc (s2, &n, NULL); + ASSERT (n == 21); + ASSERT (memcmp (ret, "The\0quick\0brown\0\0fox", n) == 0); + free (ret); + } + +- /* Test string_desc_quotearg_n. */ ++ /* Test sd_quotearg_n. */ + { +- char *ret = string_desc_quotearg_n (1, s2); ++ char *ret = sd_quotearg_n (1, s2); + ASSERT (memcmp (ret, "Thequickbrownfox", 16 + 1) == 0); + } + +- /* Test string_desc_quotearg. */ ++ /* Test sd_quotearg. */ + { +- char *ret = string_desc_quotearg (s2); ++ char *ret = sd_quotearg (s2); + ASSERT (memcmp (ret, "Thequickbrownfox", 16 + 1) == 0); + } + +- /* Test string_desc_quotearg_n_style. */ ++ /* Test sd_quotearg_n_style. */ + { +- char *ret = string_desc_quotearg_n_style (1, clocale_quoting_style, s2); ++ char *ret = sd_quotearg_n_style (1, clocale_quoting_style, s2); + ASSERT (memcmp (ret, "\"The\\0quick\\0brown\\0\\0fox\\0\"", 28 + 1) == 0 + || /* if the locale has UTF-8 encoding */ + memcmp (ret, "\342\200\230The\\0quick\\0brown\\0\\0fox\\0\342\200\231", 32 + 1) == 0); + } + +- /* Test string_desc_quotearg_style. */ ++ /* Test sd_quotearg_style. */ + { +- char *ret = string_desc_quotearg_style (clocale_quoting_style, s2); ++ char *ret = sd_quotearg_style (clocale_quoting_style, s2); + ASSERT (memcmp (ret, "\"The\\0quick\\0brown\\0\\0fox\\0\"", 28 + 1) == 0 + || /* if the locale has UTF-8 encoding */ + memcmp (ret, "\342\200\230The\\0quick\\0brown\\0\\0fox\\0\342\200\231", 32 + 1) == 0); + } + +- /* Test string_desc_quotearg_char. */ ++ /* Test sd_quotearg_char. */ + { +- char *ret = string_desc_quotearg_char (s1, ' '); ++ char *ret = sd_quotearg_char (s1, ' '); + ASSERT (memcmp (ret, "Hello world!", 12 + 1) == 0); /* ' ' not quoted?! */ + } + +- /* Test string_desc_quotearg_colon. */ ++ /* Test sd_quotearg_colon. */ + { +- char *ret = string_desc_quotearg_colon (string_desc_from_c ("a:b")); ++ char *ret = sd_quotearg_colon (sd_from_c ("a:b")); + ASSERT (memcmp (ret, "a:b", 3 + 1) == 0); /* ':' not quoted?! */ + } + +- /* Test string_desc_quotearg_n_custom. */ ++ /* Test sd_quotearg_n_custom. */ + { +- char *ret = string_desc_quotearg_n_custom (2, "<", ">", s1); ++ char *ret = sd_quotearg_n_custom (2, "<", ">", s1); + ASSERT (memcmp (ret, "", 14 + 1) == 0); + } + +- /* Test string_desc_quotearg_n_custom. */ ++ /* Test sd_quotearg_n_custom. */ + { +- char *ret = string_desc_quotearg_custom ("[[", "]]", s1); ++ char *ret = sd_quotearg_custom ("[[", "]]", s1); + ASSERT (memcmp (ret, "[[Hello world!]]", 16 + 1) == 0); + } + +--- a/tests/test-string-desc.sh ++++ b/tests/test-string-desc.sh +@@ -6,7 +6,7 @@ ${CHECKER} test-string-desc${EXEEXT} tes + printf 'Hello world!The\0quick\0brown\0\0fox\0' > test-string-desc.ok + + : "${DIFF=diff}" +-${DIFF} test-string-desc.ok test-string-desc-1.tmp || { echo "string_desc_fwrite KO" 1>&2; Exit 1; } +-${DIFF} test-string-desc.ok test-string-desc-3.tmp || { echo "string_desc_write KO" 1>&2; Exit 1; } ++${DIFF} test-string-desc.ok test-string-desc-1.tmp || { echo "sd_fwrite KO" 1>&2; Exit 1; } ++${DIFF} test-string-desc.ok test-string-desc-3.tmp || { echo "sd_write KO" 1>&2; Exit 1; } + + Exit 0 +--- a/tests/test-xstring-desc.c ++++ b/tests/test-xstring-desc.c +@@ -28,53 +28,53 @@ + int + main (void) + { +- string_desc_t s0 = string_desc_new_empty (); +- string_desc_t s1 = string_desc_from_c ("Hello world!"); +- string_desc_t s2 = string_desc_new_addr (21, "The\0quick\0brown\0\0fox"); ++ string_desc_t s0 = sd_new_empty (); ++ string_desc_t s1 = sd_from_c ("Hello world!"); ++ string_desc_t s2 = sd_new_addr (21, "The\0quick\0brown\0\0fox"); + +- /* Test xstring_desc_new. */ +- string_desc_t s4 = xstring_desc_new (5); +- string_desc_set_char_at (s4, 0, 'H'); +- string_desc_set_char_at (s4, 4, 'o'); +- string_desc_set_char_at (s4, 1, 'e'); +- string_desc_fill (s4, 2, 4, 'l'); +- ASSERT (string_desc_length (s4) == 5); +- ASSERT (string_desc_startswith (s1, s4)); ++ /* Test xsd_new. */ ++ string_desc_t s4 = xsd_new (5); ++ sd_set_char_at (s4, 0, 'H'); ++ sd_set_char_at (s4, 4, 'o'); ++ sd_set_char_at (s4, 1, 'e'); ++ sd_fill (s4, 2, 4, 'l'); ++ ASSERT (sd_length (s4) == 5); ++ ASSERT (sd_startswith (s1, s4)); + +- /* Test xstring_desc_new_filled. */ +- string_desc_t s5 = xstring_desc_new_filled (5, 'l'); +- string_desc_set_char_at (s5, 0, 'H'); +- string_desc_set_char_at (s5, 4, 'o'); +- string_desc_set_char_at (s5, 1, 'e'); +- ASSERT (string_desc_length (s5) == 5); +- ASSERT (string_desc_startswith (s1, s5)); ++ /* Test xsd_new_filled. */ ++ string_desc_t s5 = xsd_new_filled (5, 'l'); ++ sd_set_char_at (s5, 0, 'H'); ++ sd_set_char_at (s5, 4, 'o'); ++ sd_set_char_at (s5, 1, 'e'); ++ ASSERT (sd_length (s5) == 5); ++ ASSERT (sd_startswith (s1, s5)); + +- /* Test xstring_desc_copy. */ ++ /* Test xsd_copy. */ + { +- string_desc_t s6 = xstring_desc_copy (s0); +- ASSERT (string_desc_is_empty (s6)); +- string_desc_free (s6); ++ string_desc_t s6 = xsd_copy (s0); ++ ASSERT (sd_is_empty (s6)); ++ sd_free (s6); + } + { +- string_desc_t s6 = xstring_desc_copy (s2); +- ASSERT (string_desc_equals (s6, s2)); +- string_desc_free (s6); ++ string_desc_t s6 = xsd_copy (s2); ++ ASSERT (sd_equals (s6, s2)); ++ sd_free (s6); + } + +- /* Test xstring_desc_concat. */ ++ /* Test xsd_concat. */ + { + string_desc_t s8 = +- xstring_desc_concat (3, string_desc_new_addr (10, "The\0quick"), +- string_desc_new_addr (7, "brown\0"), +- string_desc_new_addr (4, "fox"), +- string_desc_new_addr (7, "unused")); +- ASSERT (string_desc_equals (s8, s2)); +- string_desc_free (s8); ++ xsd_concat (3, sd_new_addr (10, "The\0quick"), ++ sd_new_addr (7, "brown\0"), ++ sd_new_addr (4, "fox"), ++ sd_new_addr (7, "unused")); ++ ASSERT (sd_equals (s8, s2)); ++ sd_free (s8); + } + +- /* Test xstring_desc_c. */ ++ /* Test xsd_c. */ + { +- char *ptr = xstring_desc_c (s2); ++ char *ptr = xsd_c (s2); + ASSERT (ptr != NULL); + ASSERT (memcmp (ptr, "The\0quick\0brown\0\0fox\0", 22) == 0); + free (ptr); diff --git a/tools/gnulib/patches/796-vc-mtime-less-read.patch b/tools/gnulib/patches/796-vc-mtime-less-read.patch new file mode 100644 index 0000000000..3fabe10369 --- /dev/null +++ b/tools/gnulib/patches/796-vc-mtime-less-read.patch @@ -0,0 +1,44 @@ +From 60cd34886c2c9f509974239fcf64a61f9a507d14 Mon Sep 17 00:00:00 2001 +From: Bruno Haible +Date: Tue, 25 Feb 2025 09:04:28 +0100 +Subject: [PATCH] vc-mtime: Reduce number of read() system calls. + +* lib/vc-mtime.c: Include . +(git_vc_controlled): Read bytes into a buffer, not one-by-one. +--- + ChangeLog | 6 ++++++ + lib/vc-mtime.c | 15 +++++++++++---- + 2 files changed, 17 insertions(+), 4 deletions(-) + +--- a/lib/vc-mtime.c ++++ b/lib/vc-mtime.c +@@ -21,6 +21,7 @@ + /* Specification. */ + #include "vc-mtime.h" + ++#include + #include + #include + +@@ -56,11 +57,17 @@ git_vc_controlled (const char *filename) + return false; + + /* Read the subprocess output, and test whether it is non-empty. */ +- size_t count = 0; +- char c; ++ ptrdiff_t count = 0; + +- while (safe_read (fd[0], &c, 1) > 0) +- count++; ++ for (;;) ++ { ++ char buf[1024]; ++ ptrdiff_t n = safe_read (fd[0], buf, sizeof (buf)); ++ if (n > 0) ++ count += n; ++ else ++ break; ++ } + + close (fd[0]); + diff --git a/tools/gnulib/patches/797-vc-mtime-add-api.patch b/tools/gnulib/patches/797-vc-mtime-add-api.patch new file mode 100644 index 0000000000..eeb6636e67 --- /dev/null +++ b/tools/gnulib/patches/797-vc-mtime-add-api.patch @@ -0,0 +1,968 @@ +From 78269749030dde23182c29376d1410592436eb5d Mon Sep 17 00:00:00 2001 +From: Bruno Haible +Date: Thu, 1 May 2025 17:26:27 +0200 +Subject: [PATCH] vc-mtime: Add API for more efficient use of git. + +Reported by Serhii Tereshchenko, Arthur, Adam YS, Foucauld Degeorges +at . + +* lib/vc-mtime.h (max_vc_mtime): New declaration. +* lib/vc-mtime.c: Include , , , filename.h, +xalloc.h, xgetcwd.h, xvasprintf.h, gl_map.h, gl_xmap.h, gl_hash_map.h, +hashkey-string.h, unlocked-io.h. +(is_git_present): New function, extracted from vc_mtime. +(vc_mtime): Invoke it. +(MAX_COMMAND_LENGTH, MAX_CMD_LEN): New macros. +(abs_git_checkout): New function, based on execute_and_read_line in +lib/javacomp.c. +(ancestor_level, relativize): New functions. +(struct accumulator): New type. +(accumulate): New function. +(max_vc_mtime): New function. +(test_ancestor_level, test_relativize, main) [TEST]: New functions. +* modules/vc-mtime (Depends-on): Add filename, xalloc, xgetcwd, +canonicalize-lgpl, xvasprintf, str_startswith, map, xmap, hash-map, +hashkey-string, getdelim. +--- + ChangeLog | 23 ++ + lib/vc-mtime.c | 866 +++++++++++++++++++++++++++++++++++++++++++++-- + lib/vc-mtime.h | 7 + + modules/vc-mtime | 11 + + 4 files changed, 886 insertions(+), 21 deletions(-) + +--- a/lib/vc-mtime.c ++++ b/lib/vc-mtime.c +@@ -21,8 +21,11 @@ + /* Specification. */ + #include "vc-mtime.h" + ++#include + #include ++#include + #include ++#include + #include + + #include +@@ -32,11 +35,51 @@ + #include "safe-read.h" + #include "xstrtol.h" + #include "stat-time.h" ++#include "filename.h" ++#include "xalloc.h" ++#include "xgetcwd.h" ++#include "xvasprintf.h" ++#include "gl_map.h" ++#include "gl_xmap.h" ++#include "gl_hash_map.h" ++#include "hashkey-string.h" ++#if USE_UNLOCKED_IO ++# include "unlocked-io.h" ++#endif + #include "gettext.h" + + #define _(msgid) dgettext ("gnulib", msgid) + + ++/* ========================================================================== */ ++ ++/* Determines whether git is present. */ ++static bool ++is_git_present (void) ++{ ++ static bool git_tested; ++ static bool git_present; ++ ++ if (!git_tested) ++ { ++ /* Test for presence of git: ++ "git --version >/dev/null 2>/dev/null" */ ++ const char *argv[3]; ++ int exitstatus; ++ ++ argv[0] = "git"; ++ argv[1] = "--version"; ++ argv[2] = NULL; ++ exitstatus = execute ("git", "git", argv, NULL, NULL, ++ false, false, true, true, ++ true, false, NULL); ++ git_present = (exitstatus == 0); ++ git_tested = true; ++ } ++ ++ return git_present; ++} ++ + /* Determines whether the specified file is under version control. */ + static bool + git_vc_controlled (const char *filename) +@@ -178,27 +221,7 @@ git_mtime (struct timespec *mtime, const + int + vc_mtime (struct timespec *mtime, const char *filename) + { +- static bool git_tested; +- static bool git_present; +- +- if (!git_tested) +- { +- /* Test for presence of git: +- "git --version >/dev/null 2>/dev/null" */ +- const char *argv[3]; +- int exitstatus; +- +- argv[0] = "git"; +- argv[1] = "--version"; +- argv[2] = NULL; +- exitstatus = execute ("git", "git", argv, NULL, NULL, +- false, false, true, true, +- true, false, NULL); +- git_present = (exitstatus == 0); +- git_tested = true; +- } +- +- if (git_present ++ if (is_git_present () + && git_vc_controlled (filename) + && git_unmodified (filename)) + { +@@ -213,3 +236,804 @@ vc_mtime (struct timespec *mtime, const + } + return -1; + } ++ ++/* ========================================================================== */ ++ ++/* Maximum length of a command that is guaranteed to work. */ ++#if defined _WIN32 || defined __CYGWIN__ ++/* Windows */ ++# define MAX_COMMAND_LENGTH 8192 ++#else ++/* Unix platforms */ ++# define MAX_COMMAND_LENGTH 32768 ++#endif ++/* Keep some safe distance to this maximum. */ ++#define MAX_CMD_LEN ((int) (MAX_COMMAND_LENGTH * 0.8)) ++ ++/* Returns the directory name of the git checkout that contains tha current ++ directory, as an absolute file name, or NULL if the current directory is ++ not in a git checkout. */ ++static char * ++abs_git_checkout (void) ++{ ++ /* Run "git rev-parse --show-toplevel 2>/dev/null" and return its output, ++ without the trailing newline. */ ++ const char *argv[4]; ++ pid_t child; ++ int fd[1]; ++ ++ argv[0] = "git"; ++ argv[1] = "rev-parse"; ++ argv[2] = "--show-toplevel"; ++ argv[3] = NULL; ++ child = create_pipe_in ("git", "git", argv, NULL, NULL, ++ DEV_NULL, true, true, false, fd); ++ ++ if (child == -1) ++ return NULL; ++ ++ /* Retrieve its result. */ ++ FILE *fp = fdopen (fd[0], "r"); ++ if (fp == NULL) ++ error (EXIT_FAILURE, errno, _("fdopen() failed")); ++ ++ char *line = NULL; ++ size_t linesize = 0; ++ size_t linelen = getline (&line, &linesize, fp); ++ if (linelen == (size_t)(-1)) ++ { ++ fclose (fp); ++ wait_subprocess (child, "git", true, true, true, false, NULL); ++ return NULL; ++ } ++ else ++ { ++ int exitstatus; ++ ++ if (linelen > 0 && line[linelen - 1] == '\n') ++ line[linelen - 1] = '\0'; ++ ++ /* Read until EOF (otherwise the child process may get a SIGPIPE signal). */ ++ while (getc (fp) != EOF) ++ ; ++ ++ fclose (fp); ++ ++ /* Remove zombie process from process list, and retrieve exit status. */ ++ exitstatus = ++ wait_subprocess (child, "git", true, true, true, false, NULL); ++ if (exitstatus == 0) ++ return line; ++ } ++ free (line); ++ return NULL; ++} ++ ++/* Given an absolute canonicalized directory DIR1 and an absolute canonicalized ++ directory DIR2, returns N where DIR1 = DIR2 "/.." ... "/.." with N times ++ "/..", or -1 if DIR1 is not an ancestor directory of DIR2. */ ++static long ++ancestor_level (const char *dir1, const char *dir2) ++{ ++ if (strcmp (dir1, "/") == 0) ++ dir1 = ""; ++ if (strcmp (dir2, "/") == 0) ++ dir2 = ""; ++ size_t dir1_len = strlen (dir1); ++ if (strncmp (dir1, dir2, dir1_len) == 0) ++ { ++ /* DIR2 starts with DIR1. */ ++ const char *p = dir2 + dir1_len; ++ if (*p == '\0') ++ /* DIR2 and DIR1 are the same. */ ++ return 0; ++ if (ISSLASH (*p)) ++ { ++ /* Return the number of slashes in the tail of DIR2 that starts ++ at P. */ ++ long n = 1; ++ p++; ++ for (; *p != '\0'; p++) ++ if (ISSLASH (*p)) ++ n++; ++ return n; ++ } ++ } ++ return -1; ++} ++ ++/* Given an absolute canolicalized FILENAME that starts with DIR1, returns the ++ same file name relative to DIR2, where DIR1 = DIR2 "/.." ... "/.." with ++ N times "/..", as a freshly allocated string. */ ++static char * ++relativize (const char *filename, ++ unsigned long n, const char *dir1, const char *dir2) ++{ ++ if (strcmp (dir1, "/") == 0) ++ dir1 = ""; ++ size_t dir1_len = strlen (dir1); ++ if (!(strncmp (filename, dir1, dir1_len) == 0 ++ && (filename[dir1_len] == '\0' || ISSLASH (filename[dir1_len])))) ++ /* Invalid argument. */ ++ abort (); ++ if (strcmp (dir2, "/") == 0) ++ dir2 = ""; ++ ++ dir2 += dir1_len; ++ filename += dir1_len; ++ for (;;) ++ { ++ /* Invariant: The result will be N times "../" followed by FILENAME. */ ++ if (*filename == '\0') ++ break; ++ if (!ISSLASH (*filename)) ++ abort (); ++ filename++; ++ if (*dir2 == '\0') ++ break; ++ if (!ISSLASH (*dir2)) ++ abort (); ++ dir2++; ++ /* Skip one component in DIR2. */ ++ const char *dir2_s; ++ for (dir2_s = dir2; *dir2_s != '\0'; dir2_s++) ++ if (ISSLASH (*dir2_s)) ++ break; ++ /* Skip one component in FILENAME, at P. */ ++ const char *filename_s; ++ for (filename_s = filename; *filename_s != '\0'; filename_s++) ++ if (ISSLASH (*filename_s)) ++ break; ++ /* Did the components match? */ ++ if (!(filename_s - filename == dir2_s - dir2 ++ && memcmp (filename, dir2, dir2_s - dir2) == 0)) ++ break; ++ dir2 = dir2_s; ++ filename = filename_s; ++ n--; ++ } ++ ++ if (n == 0 && *filename == '\0') ++ return xstrdup ("."); ++ ++ char *result = (char *) xmalloc (3 * n + strlen (filename) + 1); ++ { ++ char *q = result; ++ for (; n > 0; n--) ++ { ++ q[0] = '.'; q[1] = '.'; q[2] = '/'; q += 3; ++ } ++ strcpy (q, filename); ++ } ++ return result; ++} ++ ++/* Accumulating mtimes. */ ++struct accumulator ++{ ++ bool has_some_mtimes; ++ struct timespec max_of_mtimes; ++}; ++ ++static void ++accumulate (struct accumulator *accu, struct timespec mtime) ++{ ++ if (accu->has_some_mtimes) ++ { ++ /* Compute the maximum of accu->max_of_mtimes and mtime. */ ++ if (accu->max_of_mtimes.tv_sec < mtime.tv_sec ++ || (accu->max_of_mtimes.tv_sec == mtime.tv_sec ++ && accu->max_of_mtimes.tv_nsec < mtime.tv_nsec)) ++ accu->max_of_mtimes = mtime; ++ } ++ else ++ { ++ accu->max_of_mtimes = mtime; ++ accu->has_some_mtimes = true; ++ } ++} ++ ++int ++max_vc_mtime (struct timespec *max_of_mtimes, ++ size_t nfiles, const char * const *filenames) ++{ ++ if (nfiles == 0) ++ /* Invalid argument. */ ++ abort (); ++ ++ struct accumulator accu = { false }; ++ ++ /* Determine which of the specified files are under version control, ++ and which are duplicates. (The case of duplicates is rare, but it needs ++ special attention, because 'git ls-files' eliminates duplicates.) ++ vc_controlled[n] = 1 means that filenames[n] is under version control. ++ vc_controlled[n] = 0 means that filenames[n] is not under version control. ++ vc_controlled[n] = -1 means that filenames[n] is a duplicate. */ ++ signed char *vc_controlled = XNMALLOC (nfiles, signed char); ++ for (size_t n = 0; n < nfiles; n++) ++ vc_controlled[n] = 0; ++ ++ if (is_git_present ()) ++ { ++ /* Since 'git ls-files' produces an error when at least one of the files ++ is outside the git checkout that contains tha current directory, we ++ need to filter out such files. This is most easily done by converting ++ each file name to a canonical file name first and then comparing with ++ the directory name of said git checkout. */ ++ char *git_checkout = abs_git_checkout (); ++ if (git_checkout != NULL) ++ { ++ char *currdir = xgetcwd (); ++ /* git_checkout is expected to be an ancestor directory of the ++ current directory. */ ++ long ancestor = ancestor_level (git_checkout, currdir); ++ if (ancestor >= 0) ++ { ++ char **canonical_filenames = XNMALLOC (nfiles, char *); ++ for (size_t n = 0; n < nfiles; n++) ++ { ++ char *canonical = canonicalize_file_name (filenames[n]); ++ if (canonical == NULL) ++ { ++ if (errno == ENOMEM) ++ xalloc_die (); ++ /* The file filenames[n] does not exist. */ ++ for (size_t k = n; k > 0; ) ++ free (canonical_filenames[--k]); ++ free (canonical_filenames); ++ free (currdir); ++ free (git_checkout); ++ free (vc_controlled); ++ return -1; ++ } ++ canonical_filenames[n] = canonical; ++ } ++ ++ /* Test which of these absolute file names are outside of the ++ git_checkout. */ ++ char *git_checkout_slash = ++ (strcmp (git_checkout, "/") == 0 ++ ? xstrdup (git_checkout) ++ : xasprintf ("%s/", git_checkout)); ++ ++ char **checkout_relative_filenames = XNMALLOC (nfiles, char *); ++ char **currdir_relative_filenames = XNMALLOC (nfiles, char *); ++ for (size_t n = 0; n < nfiles; n++) ++ { ++ if (str_startswith (canonical_filenames[n], git_checkout_slash)) ++ { ++ vc_controlled[n] = 1; ++ checkout_relative_filenames[n] = ++ relativize (canonical_filenames[n], ++ 0, git_checkout, git_checkout); ++ currdir_relative_filenames[n] = ++ relativize (canonical_filenames[n], ++ ancestor, git_checkout, currdir); ++ } ++ else ++ { ++ vc_controlled[n] = 0; ++ checkout_relative_filenames[n] = NULL; ++ currdir_relative_filenames[n] = NULL; ++ } ++ } ++ ++ /* Room for passing arguments to git commands. */ ++ const char **argv = XNMALLOC (6 + nfiles + 1, const char *); ++ ++ { ++ /* Put the relative file names into a hash table. This is needed ++ because 'git ls-files' returns the files in a different order ++ than the one we provide in the command. */ ++ gl_map_t relative_filenames_ht = ++ gl_map_create_empty (GL_HASH_MAP, ++ hashkey_string_equals, hashkey_string_hash, ++ NULL, NULL); ++ for (size_t n = 0; n < nfiles; n++) ++ if (currdir_relative_filenames[n] != NULL) ++ { ++ if (gl_map_get (relative_filenames_ht, currdir_relative_filenames[n]) != NULL) ++ { ++ /* It's already in the table. */ ++ vc_controlled[n] = -1; ++ } ++ else ++ gl_map_put (relative_filenames_ht, currdir_relative_filenames[n], &vc_controlled[n]); ++ } ++ ++ /* Run "git ls-files -c -o -t -z FILE1..." for as many files as ++ possible, and inspect the output. */ ++ size_t n0 = 0; ++ do ++ { ++ size_t i = 0; ++ argv[i++] = "git"; ++ argv[i++] = "ls-files"; ++ argv[i++] = "-c"; ++ argv[i++] = "-o"; ++ argv[i++] = "-t"; ++ argv[i++] = "-z"; ++ size_t i0 = i; ++ ++ size_t n = n0; ++ size_t cmd_len = 25; ++ for (; n < nfiles; n++) ++ { ++ if (vc_controlled[n] == 1) ++ { ++ if (cmd_len + strlen (currdir_relative_filenames[n]) >= MAX_CMD_LEN ++ && i > i0) ++ break; ++ argv[i++] = currdir_relative_filenames[n]; ++ cmd_len += 1 + strlen (currdir_relative_filenames[n]); ++ } ++ n++; ++ } ++ if (i > i0) ++ { ++ pid_t child; ++ int fd[1]; ++ ++ argv[i] = NULL; ++ child = create_pipe_in ("git", "git", argv, NULL, NULL, ++ DEV_NULL, true, true, false, fd); ++ if (child == -1) ++ break; ++ ++ /* Read the subprocess output. It is expected to be of the form ++ T1 NUL ++ T2 NUL ++ ... ++ where the relative filenames correspond to the given file ++ names (because we have already relativized them). */ ++ FILE *fp = fdopen (fd[0], "r"); ++ if (fp == NULL) ++ error (EXIT_FAILURE, errno, _("fdopen() failed")); ++ ++ char *fn = NULL; ++ size_t fn_size = 0; ++ for (;;) ++ { ++ int status = fgetc (fp); ++ if (status == EOF) ++ break; ++ /* status is a status tag, as documented in ++ "man git-ls-files". */ ++ ++ int space = fgetc (fp); ++ if (space != ' ') ++ { ++ fprintf (stderr, "vc-mtime: git ls-files output not as expected\n"); ++ break; ++ } ++ ++ if (getdelim (&fn, &fn_size, '\0', fp) == -1) ++ { ++ if (errno == ENOMEM) ++ xalloc_die (); ++ fprintf (stderr, "vc-mtime: failed to read git ls-files output\n"); ++ break; ++ } ++ signed char *vc_controlled_p = ++ (signed char *) gl_map_get (relative_filenames_ht, fn); ++ if (vc_controlled_p == NULL) ++ fprintf (stderr, "vc-mtime: git ls-files returned an unexpected file name: %s\n", fn); ++ else ++ *vc_controlled_p = (status == 'H' ? 1 : 0); ++ } ++ ++ free (fn); ++ fclose (fp); ++ ++ /* Remove zombie process from process list, and retrieve exit status. */ ++ int exitstatus = ++ wait_subprocess (child, "git", false, true, true, false, NULL); ++ if (exitstatus != 0) ++ fprintf (stderr, "vc-mtime: git ls-files failed with exit code %d\n", exitstatus); ++ } ++ n0 = n; ++ } ++ while (n0 < nfiles); ++ ++ gl_map_free (relative_filenames_ht); ++ } ++ ++ { ++ /* Put the relative file names into a hash table. This is needed ++ because 'git diff' returns the files in a different order ++ than the one we provide in the command. */ ++ gl_map_t relative_filenames_ht = ++ gl_map_create_empty (GL_HASH_MAP, ++ hashkey_string_equals, hashkey_string_hash, ++ NULL, NULL); ++ for (size_t n = 0; n < nfiles; n++) ++ if (vc_controlled[n] == 1) ++ { ++ /* No need to test for duplicates here. We have already set ++ vc_controlled[n] to -1 for duplicates, above. */ ++ gl_map_put (relative_filenames_ht, checkout_relative_filenames[n], &vc_controlled[n]); ++ } ++ ++ /* Run "git diff --name-only --no-relative -z HEAD -- FILE1..." for ++ as many files as possible, and inspect the output. */ ++ size_t n0 = 0; ++ do ++ { ++ size_t i = 0; ++ argv[i++] = "git"; ++ argv[i++] = "diff"; ++ argv[i++] = "--name-only"; ++ argv[i++] = "--no-relative"; ++ argv[i++] = "-z"; ++ argv[i++] = "HEAD"; ++ argv[i++] = "--"; ++ size_t i0 = i; ++ ++ size_t n = n0; ++ size_t cmd_len = 46; ++ for (; n < nfiles; n++) ++ { ++ if (vc_controlled[n] == 1) ++ { ++ if (cmd_len + strlen (currdir_relative_filenames[n]) >= MAX_CMD_LEN ++ && i > i0) ++ break; ++ argv[i++] = currdir_relative_filenames[n]; ++ cmd_len += 1 + strlen (currdir_relative_filenames[n]); ++ } ++ n++; ++ } ++ if (i > i0) ++ { ++ pid_t child; ++ int fd[1]; ++ ++ argv[i] = NULL; ++ child = create_pipe_in ("git", "git", argv, NULL, NULL, ++ DEV_NULL, true, true, false, fd); ++ if (child == -1) ++ break; ++ ++ /* Read the subprocess output. It is expected to be of the form ++ NUL ++ NUL ++ ... ++ where the relative filenames are relative to the git ++ checkout dir, not to currdir! */ ++ FILE *fp = fdopen (fd[0], "r"); ++ if (fp == NULL) ++ error (EXIT_FAILURE, errno, _("fdopen() failed")); ++ ++ char *fn = NULL; ++ size_t fn_size = 0; ++ for (;;) ++ { ++ /* Test for EOF. */ ++ int c = fgetc (fp); ++ if (c == EOF) ++ break; ++ ungetc (c, fp); ++ ++ if (getdelim (&fn, &fn_size, '\0', fp) == -1) ++ { ++ if (errno == ENOMEM) ++ xalloc_die (); ++ fprintf (stderr, "vc-mtime: failed to read git diff output\n"); ++ break; ++ } ++ signed char *vc_controlled_p = ++ (signed char *) gl_map_get (relative_filenames_ht, fn); ++ if (vc_controlled_p == NULL) ++ fprintf (stderr, "vc-mtime: git diff returned an unexpected file name: %s\n", fn); ++ else ++ /* filenames[n] is under version control but is modified. ++ Treat it like a file not under version control. */ ++ *vc_controlled_p = 0; ++ } ++ ++ free (fn); ++ fclose (fp); ++ ++ /* Remove zombie process from process list, and retrieve exit status. */ ++ int exitstatus = ++ wait_subprocess (child, "git", false, true, true, false, NULL); ++ if (exitstatus != 0) ++ fprintf (stderr, "vc-mtime: git diff failed with exit code %d\n", exitstatus); ++ } ++ n0 = n; ++ } ++ while (n0 < nfiles); ++ ++ gl_map_free (relative_filenames_ht); ++ } ++ ++ { ++ /* Run "git log -1 --format=%ct -- FILE1...". It prints the ++ time of last modification (the 'CommitDate', not the ++ 'AuthorDate' which merely represents the time at which the ++ author locally committed the first version of the change), ++ as the number of seconds since the Epoch. The '--' option ++ is for the case that the specified file was removed. */ ++ size_t n0 = 0; ++ do ++ { ++ size_t i = 0; ++ argv[i++] = "git"; ++ argv[i++] = "log"; ++ argv[i++] = "-1"; ++ argv[i++] = "--format=%ct"; ++ argv[i++] = "--"; ++ size_t i0 = i; ++ ++ size_t n = n0; ++ size_t cmd_len = 27; ++ for (; n < nfiles; n++) ++ { ++ if (vc_controlled[n] == 1) ++ { ++ if (cmd_len + strlen (currdir_relative_filenames[n]) >= MAX_CMD_LEN ++ && i > i0) ++ break; ++ argv[i++] = currdir_relative_filenames[n]; ++ cmd_len += 1 + strlen (currdir_relative_filenames[n]); ++ } ++ n++; ++ } ++ if (i > i0) ++ { ++ pid_t child; ++ int fd[1]; ++ ++ argv[i] = NULL; ++ child = create_pipe_in ("git", "git", argv, NULL, NULL, ++ DEV_NULL, true, true, false, fd); ++ if (child == -1) ++ break; ++ ++ /* Read the subprocess output. It is expected to be a ++ single line, containing a positive integer. */ ++ FILE *fp = fdopen (fd[0], "r"); ++ if (fp == NULL) ++ error (EXIT_FAILURE, errno, _("fdopen() failed")); ++ ++ char *line = NULL; ++ size_t linesize = 0; ++ size_t linelen = getline (&line, &linesize, fp); ++ if (linelen == (size_t)(-1)) ++ { ++ if (errno == ENOMEM) ++ xalloc_die (); ++ fprintf (stderr, "vc-mtime: failed to read git log output\n"); ++ git_log_fail1: ++ free (line); ++ fclose (fp); ++ wait_subprocess (child, "git", true, false, true, false, NULL); ++ git_log_fail2: ++ free (argv); ++ for (size_t k = nfiles; k > 0; ) ++ free (currdir_relative_filenames[--k]); ++ free (currdir_relative_filenames); ++ for (size_t k = nfiles; k > 0; ) ++ free (checkout_relative_filenames[--k]); ++ free (checkout_relative_filenames); ++ free (git_checkout_slash); ++ for (size_t k = nfiles; k > 0; ) ++ free (canonical_filenames[--k]); ++ free (canonical_filenames); ++ free (currdir); ++ free (git_checkout); ++ free (vc_controlled); ++ return -1; ++ } ++ if (linelen > 0 && line[linelen - 1] == '\n') ++ line[linelen - 1] = '\0'; ++ ++ char *endptr; ++ unsigned long git_log_time; ++ if (!(xstrtoul (line, &endptr, 10, &git_log_time, NULL) == LONGINT_OK ++ && endptr == line + strlen (line))) ++ { ++ fprintf (stderr, "vc-mtime: git log output not as expected\n"); ++ goto git_log_fail1; ++ } ++ ++ struct timespec mtime; ++ mtime.tv_sec = git_log_time; ++ mtime.tv_nsec = 0; ++ accumulate (&accu, mtime); ++ ++ free (line); ++ fclose (fp); ++ ++ /* Remove zombie process from process list, and retrieve exit status. */ ++ int exitstatus = ++ wait_subprocess (child, "git", false, true, true, false, NULL); ++ if (exitstatus != 0) ++ { ++ fprintf (stderr, "vc-mtime: git log failed with exit code %d\n", exitstatus); ++ goto git_log_fail2; ++ } ++ } ++ n0 = n; ++ } ++ while (n0 < nfiles); ++ } ++ ++ free (argv); ++ for (size_t k = nfiles; k > 0; ) ++ free (currdir_relative_filenames[--k]); ++ free (currdir_relative_filenames); ++ for (size_t k = nfiles; k > 0; ) ++ free (checkout_relative_filenames[--k]); ++ free (checkout_relative_filenames); ++ free (git_checkout_slash); ++ for (size_t k = nfiles; k > 0; ) ++ free (canonical_filenames[--k]); ++ free (canonical_filenames); ++ } ++ free (currdir); ++ } ++ free (git_checkout); ++ } ++ ++ /* For the files that are not under version control, or that are modified ++ compared to HEAD, use the file's time stamp. */ ++ for (size_t n = 0; n < nfiles; n++) ++ if (vc_controlled[n] == 0) ++ { ++ struct stat statbuf; ++ if (stat (filenames[n], &statbuf) < 0) ++ { ++ free (vc_controlled); ++ return -1; ++ } ++ ++ struct timespec mtime = get_stat_mtime (&statbuf); ++ accumulate (&accu, mtime); ++ } ++ ++ free (vc_controlled); ++ ++ /* Since nfiles > 0, we must have accumulated at least one mtime. */ ++ if (!accu.has_some_mtimes) ++ abort (); ++ *max_of_mtimes = accu.max_of_mtimes; ++ return 0; ++} ++ ++/* ========================================================================== */ ++ ++#ifdef TEST ++ ++#include ++#include ++#include ++ ++/* Some unit tests for internal functions. */ ++ ++static void ++test_ancestor_level (void) ++{ ++ assert (ancestor_level ("/home/user/projects/gnulib", "/home/user/projects/gnulib") == 0); ++ assert (ancestor_level ("/", "/") == 0); ++ ++ assert (ancestor_level ("/home/user/projects/gnulib", "/home/user/projects/gnulib/lib/crypto") == 2); ++ assert (ancestor_level ("/", "/home/user") == 2); ++ ++ assert (ancestor_level ("/home/user/.local", "/home/user/projects/gnulib") == -1); ++ assert (ancestor_level ("/.local", "/home/user") == -1); ++ assert (ancestor_level ("/.local", "/") == -1); ++} ++ ++static void ++test_relativize (void) ++{ ++ assert (strcmp (relativize ("/home/user/projects/gnulib", ++ 0, "/home/user/projects/gnulib", "/home/user/projects/gnulib"), ++ ".") == 0); ++ assert (strcmp (relativize ("/home/user/projects/gnulib/NEWS", ++ 0, "/home/user/projects/gnulib", "/home/user/projects/gnulib"), ++ "NEWS") == 0); ++ assert (strcmp (relativize ("/home/user/projects/gnulib/doc/Makefile", ++ 0, "/home/user/projects/gnulib", "/home/user/projects/gnulib"), ++ "doc/Makefile") == 0); ++ ++ assert (strcmp (relativize ("/", ++ 0, "/", "/"), ++ ".") == 0); ++ assert (strcmp (relativize ("/swapfile", ++ 0, "/", "/"), ++ "swapfile") == 0); ++ assert (strcmp (relativize ("/etc/passwd", ++ 0, "/", "/"), ++ "etc/passwd") == 0); ++ ++ assert (strcmp (relativize ("/home/user/projects/gnulib", ++ 2, "/home/user/projects/gnulib", "/home/user/projects/gnulib/lib/crypto"), ++ "../../") == 0); ++ assert (strcmp (relativize ("/home/user/projects/gnulib/lib", ++ 2, "/home/user/projects/gnulib", "/home/user/projects/gnulib/lib/crypto"), ++ "../") == 0); ++ assert (strcmp (relativize ("/home/user/projects/gnulib/lib/crypto", ++ 2, "/home/user/projects/gnulib", "/home/user/projects/gnulib/lib/crypto"), ++ ".") == 0); ++ assert (strcmp (relativize ("/home/user/projects/gnulib/lib/malloc", ++ 2, "/home/user/projects/gnulib", "/home/user/projects/gnulib/lib/crypto"), ++ "../malloc") == 0); ++ assert (strcmp (relativize ("/home/user/projects/gnulib/lib/cr", ++ 2, "/home/user/projects/gnulib", "/home/user/projects/gnulib/lib/crypto"), ++ "../cr") == 0); ++ assert (strcmp (relativize ("/home/user/projects/gnulib/lib/cryptography", ++ 2, "/home/user/projects/gnulib", "/home/user/projects/gnulib/lib/crypto"), ++ "../cryptography") == 0); ++ assert (strcmp (relativize ("/home/user/projects/gnulib/doc", ++ 2, "/home/user/projects/gnulib", "/home/user/projects/gnulib/lib/crypto"), ++ "../../doc") == 0); ++ assert (strcmp (relativize ("/home/user/projects/gnulib/doc/Makefile", ++ 2, "/home/user/projects/gnulib", "/home/user/projects/gnulib/lib/crypto"), ++ "../../doc/Makefile") == 0); ++ ++ assert (strcmp (relativize ("/", ++ 2, "/", "/home/user"), ++ "../../") == 0); ++ assert (strcmp (relativize ("/home", ++ 2, "/", "/home/user"), ++ "../") == 0); ++ assert (strcmp (relativize ("/home/user", ++ 2, "/", "/home/user"), ++ ".") == 0); ++ assert (strcmp (relativize ("/home/root", ++ 2, "/", "/home/user"), ++ "../root") == 0); ++ assert (strcmp (relativize ("/home/us", ++ 2, "/", "/home/user"), ++ "../us") == 0); ++ assert (strcmp (relativize ("/home/users", ++ 2, "/", "/home/user"), ++ "../users") == 0); ++ assert (strcmp (relativize ("/etc", ++ 2, "/", "/home/user"), ++ "../../etc") == 0); ++ assert (strcmp (relativize ("/etc/passwd", ++ 2, "/", "/home/user"), ++ "../../etc/passwd") == 0); ++} ++ ++/* Usage: ./a.out FILE[...] ++ */ ++int ++main (int argc, char *argv[]) ++{ ++ test_ancestor_level (); ++ test_relativize (); ++ ++ if (argc == 1) ++ { ++ fprintf (stderr, "Usage: ./a.out FILE[...]\n"); ++ return 1; ++ } ++ struct timespec mtime; ++ int ret = max_vc_mtime (&mtime, argc - 1, (const char **) argv + 1); ++ if (ret == 0) ++ { ++ time_t t = mtime.tv_sec; ++ struct tm *gmt = gmtime (&t); ++ printf ("mtime = %04d-%02d-%02d %02d:%02d:%02d UTC\n", ++ gmt->tm_year + 1900, gmt->tm_mon + 1, gmt->tm_mday, ++ gmt->tm_hour, gmt->tm_min, gmt->tm_sec); ++ return 0; ++ } ++ else ++ { ++ printf ("failed\n"); ++ return 1; ++ } ++} ++ ++/* ++ * Local Variables: ++ * compile-command: "gcc -ggdb -DTEST -Wall -I. -I.. vc-mtime.c libgnu.a" ++ * End: ++ */ ++ ++#endif +--- a/lib/vc-mtime.h ++++ b/lib/vc-mtime.h +@@ -90,6 +90,13 @@ extern "C" { + Upon failure, it returns -1. */ + extern int vc_mtime (struct timespec *mtime, const char *filename); + ++/* Determines the maximum of the version-controlled modification times of ++ FILENAMES[0..NFILES-1], and returns 0. ++ Upon failure, it returns -1. ++ NFILES must be > 0. */ ++extern int max_vc_mtime (struct timespec *max_of_mtimes, ++ size_t nfiles, const char * const *filenames); ++ + #ifdef __cplusplus + } + #endif +--- a/modules/vc-mtime ++++ b/modules/vc-mtime +@@ -16,6 +16,17 @@ error + getline + xstrtol + stat-time ++filename ++xalloc ++xgetcwd ++canonicalize-lgpl ++xvasprintf ++str_startswith ++map ++xmap ++hash-map ++hashkey-string ++getdelim + gettext-h + gnulib-i18n + diff --git a/tools/gnulib/patches/798-vc-mtime-add-api.patch b/tools/gnulib/patches/798-vc-mtime-add-api.patch new file mode 100644 index 0000000000..2cf7edab4e --- /dev/null +++ b/tools/gnulib/patches/798-vc-mtime-add-api.patch @@ -0,0 +1,91 @@ +From f4c40c2d6aabef8e587176bbf5226c8bc6649574 Mon Sep 17 00:00:00 2001 +From: Bruno Haible +Date: Fri, 2 May 2025 02:43:23 +0200 +Subject: [PATCH] vc-mtime: Add API for more efficient use of git, part 2. + +* lib/vc-mtime.c (max_vc_mtime): Don't skip the odd-numbered arguments. +--- + ChangeLog | 5 +++++ + lib/vc-mtime.c | 57 +++++++++++++++++++++----------------------------- + 2 files changed, 29 insertions(+), 33 deletions(-) + +--- a/lib/vc-mtime.c ++++ b/lib/vc-mtime.c +@@ -558,17 +558,14 @@ max_vc_mtime (struct timespec *max_of_mt + size_t n = n0; + size_t cmd_len = 25; + for (; n < nfiles; n++) +- { +- if (vc_controlled[n] == 1) +- { +- if (cmd_len + strlen (currdir_relative_filenames[n]) >= MAX_CMD_LEN +- && i > i0) +- break; +- argv[i++] = currdir_relative_filenames[n]; +- cmd_len += 1 + strlen (currdir_relative_filenames[n]); +- } +- n++; +- } ++ if (vc_controlled[n] == 1) ++ { ++ if (cmd_len + strlen (currdir_relative_filenames[n]) >= MAX_CMD_LEN ++ && i > i0) ++ break; ++ argv[i++] = currdir_relative_filenames[n]; ++ cmd_len += 1 + strlen (currdir_relative_filenames[n]); ++ } + if (i > i0) + { + pid_t child; +@@ -672,17 +669,14 @@ max_vc_mtime (struct timespec *max_of_mt + size_t n = n0; + size_t cmd_len = 46; + for (; n < nfiles; n++) +- { +- if (vc_controlled[n] == 1) +- { +- if (cmd_len + strlen (currdir_relative_filenames[n]) >= MAX_CMD_LEN +- && i > i0) +- break; +- argv[i++] = currdir_relative_filenames[n]; +- cmd_len += 1 + strlen (currdir_relative_filenames[n]); +- } +- n++; +- } ++ if (vc_controlled[n] == 1) ++ { ++ if (cmd_len + strlen (currdir_relative_filenames[n]) >= MAX_CMD_LEN ++ && i > i0) ++ break; ++ argv[i++] = currdir_relative_filenames[n]; ++ cmd_len += 1 + strlen (currdir_relative_filenames[n]); ++ } + if (i > i0) + { + pid_t child; +@@ -768,17 +762,14 @@ max_vc_mtime (struct timespec *max_of_mt + size_t n = n0; + size_t cmd_len = 27; + for (; n < nfiles; n++) +- { +- if (vc_controlled[n] == 1) +- { +- if (cmd_len + strlen (currdir_relative_filenames[n]) >= MAX_CMD_LEN +- && i > i0) +- break; +- argv[i++] = currdir_relative_filenames[n]; +- cmd_len += 1 + strlen (currdir_relative_filenames[n]); +- } +- n++; +- } ++ if (vc_controlled[n] == 1) ++ { ++ if (cmd_len + strlen (currdir_relative_filenames[n]) >= MAX_CMD_LEN ++ && i > i0) ++ break; ++ argv[i++] = currdir_relative_filenames[n]; ++ cmd_len += 1 + strlen (currdir_relative_filenames[n]); ++ } + if (i > i0) + { + pid_t child; diff --git a/tools/gnulib/patches/799-vc-mtime-old-git.patch b/tools/gnulib/patches/799-vc-mtime-old-git.patch new file mode 100644 index 0000000000..4c2082504b --- /dev/null +++ b/tools/gnulib/patches/799-vc-mtime-old-git.patch @@ -0,0 +1,125 @@ +From 47548a77525a0f4489c9c420ccc2159079365da8 Mon Sep 17 00:00:00 2001 +From: Bruno Haible +Date: Fri, 2 May 2025 12:09:40 +0200 +Subject: [PATCH] vc-mtime: Make it work with git versions < 2.28. + +* lib/vc-mtime.c (git_version): New variable. +(is_git_present): Read the output of "git --version", and set +git_version. +(max_vc_mtime): Don't pass option --no-relative if the git version +is < 2.28. +--- + ChangeLog | 9 ++++++ + lib/vc-mtime.c | 82 +++++++++++++++++++++++++++++++++++++++++++++----- + 2 files changed, 83 insertions(+), 8 deletions(-) + +--- a/lib/vc-mtime.c ++++ b/lib/vc-mtime.c +@@ -53,7 +53,9 @@ + + /* ========================================================================== */ + +-/* Determines whether git is present. */ ++static const char *git_version; ++ ++/* Determines whether git is present, and sets git_version if so. */ + static bool + is_git_present (void) + { +@@ -63,17 +65,67 @@ is_git_present (void) + if (!git_tested) + { + /* Test for presence of git: +- "git --version >/dev/null 2>/dev/null" */ ++ "git --version 2>/dev/null" */ + const char *argv[3]; +- int exitstatus; ++ pid_t child; ++ int fd[1]; + + argv[0] = "git"; + argv[1] = "--version"; + argv[2] = NULL; +- exitstatus = execute ("git", "git", argv, NULL, NULL, +- false, false, true, true, +- true, false, NULL); +- git_present = (exitstatus == 0); ++ child = create_pipe_in ("git", "git", argv, NULL, NULL, ++ DEV_NULL, true, true, false, fd); ++ if (child == -1) ++ git_present = false; ++ else ++ { ++ /* Retrieve its result. */ ++ FILE *fp = fdopen (fd[0], "r"); ++ if (fp == NULL) ++ error (EXIT_FAILURE, errno, _("fdopen() failed")); ++ ++ char *line = NULL; ++ size_t linesize = 0; ++ size_t linelen = getline (&line, &linesize, fp); ++ if (linelen == (size_t)(-1)) ++ { ++ fclose (fp); ++ wait_subprocess (child, "git", true, true, true, false, NULL); ++ git_present = false; ++ } ++ else ++ { ++ if (linelen > 0 && line[linelen - 1] == '\n') ++ line[linelen - 1] = '\0'; ++ ++ /* Read until EOF (otherwise the child process may get a SIGPIPE ++ signal). */ ++ while (getc (fp) != EOF) ++ ; ++ ++ fclose (fp); ++ ++ /* Remove zombie process from process list, and retrieve exit ++ status. */ ++ int exitstatus = ++ wait_subprocess (child, "git", true, true, true, false, NULL); ++ if (exitstatus != 0) ++ { ++ free (line); ++ git_present = false; ++ } ++ else ++ { ++ /* The version starts at the first digit in the line. */ ++ const char *p = line; ++ for (; *p != '0'; p++) ++ if (*p >= '0' && *p <= '9') ++ break; ++ git_version = p; ++ git_present = true; ++ } ++ } ++ } + git_tested = true; + } + +@@ -660,7 +712,21 @@ max_vc_mtime (struct timespec *max_of_mt + argv[i++] = "git"; + argv[i++] = "diff"; + argv[i++] = "--name-only"; +- argv[i++] = "--no-relative"; ++ /* With git versions >= 2.28, we pass option --no-relative, ++ in order to neutralize any possible customization of the ++ "diff.relative" property. With git versions < 2.28, this ++ is not needed, and the option --no-relative does not ++ exist. */ ++ if (!(git_version[0] <= '1' ++ || (git_version[0] == '2' && git_version[1] == '.' ++ && ((git_version[2] >= '0' && git_version[2] <= '9' ++ && !(git_version[3] >= '0' && git_version[3] <= '9')) ++ || (((git_version[2] == '1' ++ && git_version[3] >= '0' && git_version[3] <= '9') ++ || (git_version[2] == '2' ++ && git_version[3] >= '0' && git_version[3] <= '7')) ++ && !(git_version[4] >= '0' && git_version[4] <= '9')))))) ++ argv[i++] = "--no-relative"; + argv[i++] = "-z"; + argv[i++] = "HEAD"; + argv[i++] = "--"; diff --git a/tools/gnulib/patches/900-str_startswith-module.patch b/tools/gnulib/patches/900-str_startswith-module.patch new file mode 100644 index 0000000000..c9af62710a --- /dev/null +++ b/tools/gnulib/patches/900-str_startswith-module.patch @@ -0,0 +1,117 @@ +From 24010120fab36721caaf92be076655571e44da07 Mon Sep 17 00:00:00 2001 +From: Bruno Haible +Date: Fri, 3 Jan 2025 09:26:14 +0100 +Subject: [PATCH] str_startswith: New module. + +* lib/string.in.h (str_startswith): New declaration. +* lib/str_startswith.c: New file. +* m4/string_h.m4 (gl_STRING_H_REQUIRE_DEFAULTS): Initialize +GNULIB_STR_STARTSWITH. +* modules/string-h (Makefile.am): Substitute GNULIB_STR_STARTSWITH. +* modules/str_startswith: New file. +--- + ChangeLog | 10 ++++++++++ + lib/str_startswith.c | 29 +++++++++++++++++++++++++++++ + lib/string.in.h | 8 ++++++++ + m4/string_h.m4 | 3 ++- + modules/str_startswith | 23 +++++++++++++++++++++++ + modules/string-h | 1 + + 6 files changed, 73 insertions(+), 1 deletion(-) + create mode 100644 lib/str_startswith.c + create mode 100644 modules/str_startswith + +--- /dev/null ++++ b/lib/str_startswith.c +@@ -0,0 +1,29 @@ ++/* str_startswith function. ++ Copyright (C) 2025 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 3 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 Bruno Haible , 2025. */ ++ ++#include "config.h" ++ ++/* Specification. */ ++#include ++ ++ ++int ++str_startswith (const char *string, const char *prefix) ++{ ++ return strncmp (string, prefix, strlen (prefix)) == 0; ++} +--- a/lib/string.in.h ++++ b/lib/string.in.h +@@ -1079,6 +1079,14 @@ _GL_WARN_ON_USE (strtok_r, "strtok_r is + /* The following functions are not specified by POSIX. They are gnulib + extensions. */ + ++#if @GNULIB_STR_STARTSWITH@ ++/* Returns true if STRING starts with PREFIX. ++ Returns false otherwise. */ ++_GL_EXTERN_C int str_startswith (const char *string, const char *prefix) ++ _GL_ATTRIBUTE_PURE ++ _GL_ARG_NONNULL ((1, 2)); ++#endif ++ + #if @GNULIB_MBSLEN@ + /* Return the number of multibyte characters in the character string STRING. + This considers multibyte characters, unlike strlen, which counts bytes. */ +--- a/m4/string_h.m4 ++++ b/m4/string_h.m4 +@@ -70,6 +70,7 @@ AC_DEFUN([gl_STRING_H_REQUIRE_DEFAULTS], + gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STRSTR]) + gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STRCASESTR]) + gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STRTOK_R]) ++ gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STR_STARTSWITH]) + gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_MBSLEN]) + gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_MBSNLEN]) + gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_MBSCHR]) +--- /dev/null ++++ b/modules/str_startswith +@@ -0,0 +1,23 @@ ++Description: ++str_startswith() function: test whether a string starts with a given prefix. ++ ++Files: ++lib/str_startswith.c ++ ++Depends-on: ++string-h ++ ++configure.ac: ++gl_STRING_MODULE_INDICATOR([str_startswith]) ++ ++Makefile.am: ++lib_SOURCES += str_startswith.c ++ ++Include: ++ ++ ++License: ++LGPLv2+ ++ ++Maintainer: ++all +--- a/modules/string-h ++++ b/modules/string-h +@@ -69,6 +69,7 @@ string.h: string.in.h $(top_builddir)/co + -e 's/@''GNULIB_STRSTR''@/$(GNULIB_STRSTR)/g' \ + -e 's/@''GNULIB_STRCASESTR''@/$(GNULIB_STRCASESTR)/g' \ + -e 's/@''GNULIB_STRTOK_R''@/$(GNULIB_STRTOK_R)/g' \ ++ -e 's/@''GNULIB_STR_STARTSWITH''@/$(GNULIB_STR_STARTSWITH)/g' \ + -e 's/@''GNULIB_STRERROR''@/$(GNULIB_STRERROR)/g' \ + -e 's/@''GNULIB_STRERROR_R''@/$(GNULIB_STRERROR_R)/g' \ + -e 's/@''GNULIB_STRERRORNAME_NP''@/$(GNULIB_STRERRORNAME_NP)/g' \ diff --git a/tools/gnulib/patches/901-str_endswith-module.patch b/tools/gnulib/patches/901-str_endswith-module.patch new file mode 100644 index 0000000000..00db5cdce0 --- /dev/null +++ b/tools/gnulib/patches/901-str_endswith-module.patch @@ -0,0 +1,119 @@ +From d89ac9373d9748f7601babf52c9129fcbcf0c907 Mon Sep 17 00:00:00 2001 +From: Bruno Haible +Date: Fri, 3 Jan 2025 09:54:14 +0100 +Subject: [PATCH] str_endswith: New module. + +* lib/string.in.h (str_endswith): New declaration. +* lib/str_endswith.c: New file. +* m4/string_h.m4 (gl_STRING_H_REQUIRE_DEFAULTS): Initialize +GNULIB_STR_ENDSWITH. +* modules/string-h (Makefile.am): Substitute GNULIB_STR_ENDSWITH. +* modules/str_endswith: New file. +--- + ChangeLog | 10 ++++++++++ + lib/str_endswith.c | 31 +++++++++++++++++++++++++++++++ + lib/string.in.h | 8 ++++++++ + m4/string_h.m4 | 3 ++- + modules/str_endswith | 23 +++++++++++++++++++++++ + modules/string-h | 1 + + 6 files changed, 75 insertions(+), 1 deletion(-) + create mode 100644 lib/str_endswith.c + create mode 100644 modules/str_endswith + +--- /dev/null ++++ b/lib/str_endswith.c +@@ -0,0 +1,31 @@ ++/* str_endswith function. ++ Copyright (C) 2025 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 3 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 Bruno Haible , 2025. */ ++ ++#include "config.h" ++ ++/* Specification. */ ++#include ++ ++ ++int ++str_endswith (const char *string, const char *suffix) ++{ ++ size_t len = strlen (string); ++ size_t n = strlen (suffix); ++ return len >= n && strcmp (string + len - n, suffix) == 0; ++} +--- a/lib/string.in.h ++++ b/lib/string.in.h +@@ -1087,6 +1087,14 @@ _GL_EXTERN_C int str_startswith (const c + _GL_ARG_NONNULL ((1, 2)); + #endif + ++#if @GNULIB_STR_ENDSWITH@ ++/* Returns true if STRING ends with SUFFIX. ++ Returns false otherwise. */ ++_GL_EXTERN_C int str_endswith (const char *string, const char *prefix) ++ _GL_ATTRIBUTE_PURE ++ _GL_ARG_NONNULL ((1, 2)); ++#endif ++ + #if @GNULIB_MBSLEN@ + /* Return the number of multibyte characters in the character string STRING. + This considers multibyte characters, unlike strlen, which counts bytes. */ +--- a/m4/string_h.m4 ++++ b/m4/string_h.m4 +@@ -71,6 +71,7 @@ AC_DEFUN([gl_STRING_H_REQUIRE_DEFAULTS], + gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STRCASESTR]) + gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STRTOK_R]) + gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STR_STARTSWITH]) ++ gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STR_ENDSWITH]) + gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_MBSLEN]) + gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_MBSNLEN]) + gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_MBSCHR]) +--- /dev/null ++++ b/modules/str_endswith +@@ -0,0 +1,23 @@ ++Description: ++str_endswith() function: test whether a string ends with a given suffix. ++ ++Files: ++lib/str_endswith.c ++ ++Depends-on: ++string-h ++ ++configure.ac: ++gl_STRING_MODULE_INDICATOR([str_endswith]) ++ ++Makefile.am: ++lib_SOURCES += str_endswith.c ++ ++Include: ++ ++ ++License: ++LGPLv2+ ++ ++Maintainer: ++all +--- a/modules/string-h ++++ b/modules/string-h +@@ -69,6 +69,7 @@ string.h: string.in.h $(top_builddir)/co + -e 's/@''GNULIB_STRSTR''@/$(GNULIB_STRSTR)/g' \ + -e 's/@''GNULIB_STRCASESTR''@/$(GNULIB_STRCASESTR)/g' \ + -e 's/@''GNULIB_STRTOK_R''@/$(GNULIB_STRTOK_R)/g' \ ++ -e 's/@''GNULIB_STR_ENDSWITH''@/$(GNULIB_STR_ENDSWITH)/g' \ + -e 's/@''GNULIB_STR_STARTSWITH''@/$(GNULIB_STR_STARTSWITH)/g' \ + -e 's/@''GNULIB_STRERROR''@/$(GNULIB_STRERROR)/g' \ + -e 's/@''GNULIB_STRERROR_R''@/$(GNULIB_STRERROR_R)/g' \