elf: Add a way to check if tunable is set (BZ 27069)
[glibc.git] / elf / stringtable.c
blob9d562c0f857c7a6eaac575ac0cee1916fabd97b4
1 /* String tables for ld.so.cache construction. Implementation.
2 Copyright (C) 2020-2023 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, see <https://www.gnu.org/licenses/>. */
18 #include <assert.h>
19 #include <error.h>
20 #include <ldconfig.h>
21 #include <libintl.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <stringtable.h>
26 static void
27 stringtable_init (struct stringtable *table)
29 table->count = 0;
31 /* This needs to be a power of two. 128 is sufficient to keep track
32 of 42 DSOs without resizing (assuming two strings per DSOs).
33 glibc itself comes with more than 20 DSOs, so 64 would likely to
34 be too small. */
35 table->allocated = 128;
37 table->entries = xcalloc (table->allocated, sizeof (table->entries[0]));
40 /* 32-bit FNV-1a hash function. */
41 static uint32_t
42 fnv1a (const char *string, size_t length)
44 const unsigned char *p = (const unsigned char *) string;
45 uint32_t hash = 2166136261U;
46 for (size_t i = 0; i < length; ++i)
48 hash ^= p[i];
49 hash *= 16777619U;
51 return hash;
54 /* Double the capacity of the hash table. */
55 static void
56 stringtable_rehash (struct stringtable *table)
58 /* This computation cannot overflow because the old total in-memory
59 size of the hash table is larger than the computed value. */
60 uint32_t new_allocated = table->allocated * 2;
61 struct stringtable_entry **new_entries
62 = xcalloc (new_allocated, sizeof (table->entries[0]));
64 uint32_t mask = new_allocated - 1;
65 for (uint32_t i = 0; i < table->allocated; ++i)
66 for (struct stringtable_entry *e = table->entries[i]; e != NULL; )
68 struct stringtable_entry *next = e->next;
69 uint32_t hash = fnv1a (e->string, e->length);
70 uint32_t new_index = hash & mask;
71 e->next = new_entries[new_index];
72 new_entries[new_index] = e;
73 e = next;
76 free (table->entries);
77 table->entries = new_entries;
78 table->allocated = new_allocated;
81 struct stringtable_entry *
82 stringtable_add (struct stringtable *table, const char *string)
84 /* Check for a zero-initialized table. */
85 if (table->allocated == 0)
86 stringtable_init (table);
88 size_t length = strlen (string);
89 if (length > (1U << 30))
90 error (EXIT_FAILURE, 0, _("String table string is too long"));
91 uint32_t hash = fnv1a (string, length);
93 /* Return a previously-existing entry. */
94 for (struct stringtable_entry *e
95 = table->entries[hash & (table->allocated - 1)];
96 e != NULL; e = e->next)
97 if (e->length == length && memcmp (e->string, string, length) == 0)
98 return e;
100 /* Increase the size of the table if necessary. Keep utilization
101 below two thirds. */
102 if (table->count >= (1U << 30))
103 error (EXIT_FAILURE, 0, _("String table has too many entries"));
104 if (table->count * 3 > table->allocated * 2)
105 stringtable_rehash (table);
107 /* Add the new table entry. */
108 ++table->count;
109 struct stringtable_entry *e
110 = xmalloc (offsetof (struct stringtable_entry, string) + length + 1);
111 uint32_t index = hash & (table->allocated - 1);
112 e->next = table->entries[index];
113 table->entries[index] = e;
114 e->length = length;
115 e->offset = 0;
116 memcpy (e->string, string, length + 1);
117 return e;
120 /* Sort reversed strings in reverse lexicographic order. This is used
121 for tail merging. */
122 static int
123 finalize_compare (const void *l, const void *r)
125 struct stringtable_entry *left = *(struct stringtable_entry **) l;
126 struct stringtable_entry *right = *(struct stringtable_entry **) r;
127 size_t to_compare;
128 if (left->length < right->length)
129 to_compare = left->length;
130 else
131 to_compare = right->length;
132 for (size_t i = 1; i <= to_compare; ++i)
134 unsigned char lch = left->string[left->length - i];
135 unsigned char rch = right->string[right->length - i];
136 if (lch != rch)
137 return rch - lch;
139 if (left->length == right->length)
140 return 0;
141 else if (left->length < right->length)
142 /* Longer strings should come first. */
143 return 1;
144 else
145 return -1;
148 void
149 stringtable_finalize (struct stringtable *table,
150 struct stringtable_finalized *result)
152 if (table->count == 0)
154 result->strings = xstrdup ("");
155 result->size = 0;
156 return;
159 /* Optimize the order of the strings. */
160 struct stringtable_entry **array = xcalloc (table->count, sizeof (*array));
162 size_t j = 0;
163 for (uint32_t i = 0; i < table->allocated; ++i)
164 for (struct stringtable_entry *e = table->entries[i]; e != NULL;
165 e = e->next)
167 array[j] = e;
168 ++j;
170 assert (j == table->count);
172 qsort (array, table->count, sizeof (*array), finalize_compare);
174 /* Assign offsets, using tail merging (sharing suffixes) if possible. */
175 array[0]->offset = 0;
176 for (uint32_t j = 1; j < table->count; ++j)
178 struct stringtable_entry *previous = array[j - 1];
179 struct stringtable_entry *current = array[j];
180 if (previous->length >= current->length
181 && memcmp (&previous->string[previous->length - current->length],
182 current->string, current->length) == 0)
183 current->offset = (previous->offset + previous->length
184 - current->length);
185 else if (__builtin_add_overflow (previous->offset,
186 previous->length + 1,
187 &current->offset))
188 error (EXIT_FAILURE, 0, _("String table is too large"));
191 /* Allocate the result string. */
193 struct stringtable_entry *last = array[table->count - 1];
194 if (__builtin_add_overflow (last->offset, last->length + 1,
195 &result->size))
196 error (EXIT_FAILURE, 0, _("String table is too large"));
198 /* The strings are copied from the hash table, so the array is no
199 longer needed. */
200 free (array);
201 result->strings = xcalloc (result->size, 1);
203 /* Copy the strings. */
204 for (uint32_t i = 0; i < table->allocated; ++i)
205 for (struct stringtable_entry *e = table->entries[i]; e != NULL;
206 e = e->next)
207 if (result->strings[e->offset] == '\0')
208 memcpy (&result->strings[e->offset], e->string, e->length + 1);