malloc/Makefile: Split and sort tests
[glibc.git] / iconv / gconv_cache.c
blob4d4034c3cbea25ac531b2f6c6581d73e5ef2d604
1 /* Cache handling for iconv modules.
2 Copyright (C) 2001-2024 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
10 The GNU C Library 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 GNU
13 Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; if not, see
17 <https://www.gnu.org/licenses/>. */
19 #include <dlfcn.h>
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <sys/mman.h>
26 #include <sys/stat.h>
28 #include <gconv_int.h>
29 #include <iconvconfig.h>
30 #include <not-cancel.h>
31 #include <pointer_guard.h>
33 #include "../intl/hash-string.h"
35 static void *gconv_cache;
36 static size_t cache_size;
37 static int cache_malloced;
40 void *
41 __gconv_get_cache (void)
43 return gconv_cache;
47 int
48 __gconv_load_cache (void)
50 int fd;
51 struct __stat64_t64 st;
52 struct gconvcache_header *header;
54 /* We cannot use the cache if the GCONV_PATH environment variable is
55 set. */
56 __gconv_path_envvar = getenv ("GCONV_PATH");
57 if (__gconv_path_envvar != NULL)
58 return -1;
60 /* See whether the cache file exists. */
61 fd = __open_nocancel (GCONV_MODULES_CACHE, O_RDONLY | O_CLOEXEC, 0);
62 if (__builtin_expect (fd, 0) == -1)
63 /* Not available. */
64 return -1;
66 /* Get information about the file. */
67 if (__glibc_unlikely (__fstat64_time64 (fd, &st) < 0)
68 /* We do not have to start looking at the file if it cannot contain
69 at least the cache header. */
70 || (size_t) st.st_size < sizeof (struct gconvcache_header))
72 close_and_exit:
73 __close_nocancel_nostatus (fd);
74 return -1;
77 /* Make the file content available. */
78 cache_size = st.st_size;
79 #ifdef _POSIX_MAPPED_FILES
80 gconv_cache = __mmap (NULL, cache_size, PROT_READ, MAP_SHARED, fd, 0);
81 if (__glibc_unlikely (gconv_cache == MAP_FAILED))
82 #endif
84 size_t already_read;
86 gconv_cache = malloc (cache_size);
87 if (gconv_cache == NULL)
88 goto close_and_exit;
90 already_read = 0;
93 ssize_t n = __read (fd, (char *) gconv_cache + already_read,
94 cache_size - already_read);
95 if (__builtin_expect (n, 0) == -1)
97 free (gconv_cache);
98 gconv_cache = NULL;
99 goto close_and_exit;
102 already_read += n;
104 while (already_read < cache_size);
106 cache_malloced = 1;
109 /* We don't need the file descriptor anymore. */
110 __close_nocancel_nostatus (fd);
112 /* Check the consistency. */
113 header = (struct gconvcache_header *) gconv_cache;
114 if (__builtin_expect (header->magic, GCONVCACHE_MAGIC) != GCONVCACHE_MAGIC
115 || __builtin_expect (header->string_offset >= cache_size, 0)
116 || __builtin_expect (header->hash_offset >= cache_size, 0)
117 || __builtin_expect (header->hash_size == 0, 0)
118 || __builtin_expect ((header->hash_offset
119 + header->hash_size * sizeof (struct hash_entry))
120 > cache_size, 0)
121 || __builtin_expect (header->module_offset >= cache_size, 0)
122 || __builtin_expect (header->otherconv_offset > cache_size, 0))
124 if (cache_malloced)
126 free (gconv_cache);
127 cache_malloced = 0;
129 #ifdef _POSIX_MAPPED_FILES
130 else
131 __munmap (gconv_cache, cache_size);
132 #endif
133 gconv_cache = NULL;
135 return -1;
138 /* That worked. */
139 return 0;
143 static int
144 find_module_idx (const char *str, size_t *idxp)
146 unsigned int idx;
147 unsigned int hval;
148 unsigned int hval2;
149 const struct gconvcache_header *header;
150 const char *strtab;
151 const struct hash_entry *hashtab;
152 unsigned int limit;
154 header = (const struct gconvcache_header *) gconv_cache;
155 strtab = (char *) gconv_cache + header->string_offset;
156 hashtab = (struct hash_entry *) ((char *) gconv_cache
157 + header->hash_offset);
159 hval = __hash_string (str);
160 idx = hval % header->hash_size;
161 hval2 = 1 + hval % (header->hash_size - 2);
163 limit = cache_size - header->string_offset;
164 while (hashtab[idx].string_offset != 0)
165 if (hashtab[idx].string_offset < limit
166 && strcmp (str, strtab + hashtab[idx].string_offset) == 0)
168 *idxp = hashtab[idx].module_idx;
169 return 0;
171 else
172 if ((idx += hval2) >= header->hash_size)
173 idx -= header->hash_size;
175 /* Nothing found. */
176 return -1;
180 #ifndef STATIC_GCONV
181 static int
182 find_module (const char *directory, const char *filename,
183 struct __gconv_step *result)
185 size_t dirlen = strlen (directory);
186 size_t fnamelen = strlen (filename) + 1;
187 char fullname[dirlen + fnamelen];
188 int status = __GCONV_NOCONV;
190 memcpy (__mempcpy (fullname, directory, dirlen), filename, fnamelen);
192 result->__shlib_handle = __gconv_find_shlib (fullname);
193 if (result->__shlib_handle != NULL)
195 status = __GCONV_OK;
197 result->__modname = NULL;
198 result->__fct = result->__shlib_handle->fct;
199 result->__init_fct = result->__shlib_handle->init_fct;
200 result->__end_fct = result->__shlib_handle->end_fct;
202 /* These settings can be overridden by the init function. */
203 result->__btowc_fct = NULL;
204 result->__data = NULL;
206 /* Call the init function. */
207 __gconv_init_fct init_fct = result->__init_fct;
208 PTR_DEMANGLE (init_fct);
209 if (init_fct != NULL)
211 status = DL_CALL_FCT (init_fct, (result));
212 PTR_MANGLE (result->__btowc_fct);
216 return status;
218 #endif
222 __gconv_compare_alias_cache (const char *name1, const char *name2, int *result)
224 size_t name1_idx;
225 size_t name2_idx;
227 if (gconv_cache == NULL)
228 return -1;
230 if (find_module_idx (name1, &name1_idx) != 0
231 || find_module_idx (name2, &name2_idx) != 0)
232 *result = strcmp (name1, name2);
233 else
234 *result = (int) (name1_idx - name2_idx);
236 return 0;
241 __gconv_lookup_cache (const char *toset, const char *fromset,
242 struct __gconv_step **handle, size_t *nsteps, int flags)
244 const struct gconvcache_header *header;
245 const char *strtab;
246 size_t fromidx;
247 size_t toidx;
248 const struct module_entry *modtab;
249 const struct module_entry *from_module;
250 const struct module_entry *to_module;
251 struct __gconv_step *result;
253 if (gconv_cache == NULL)
254 /* We have no cache available. */
255 return __GCONV_NODB;
257 header = (const struct gconvcache_header *) gconv_cache;
258 strtab = (char *) gconv_cache + header->string_offset;
259 modtab = (const struct module_entry *) ((char *) gconv_cache
260 + header->module_offset);
262 if (find_module_idx (fromset, &fromidx) != 0
263 || (header->module_offset + (fromidx + 1) * sizeof (struct module_entry)
264 > cache_size))
265 return __GCONV_NOCONV;
266 from_module = &modtab[fromidx];
268 if (find_module_idx (toset, &toidx) != 0
269 || (header->module_offset + (toidx + 1) * sizeof (struct module_entry)
270 > cache_size))
271 return __GCONV_NOCONV;
272 to_module = &modtab[toidx];
274 /* Avoid copy-only transformations if the user requests. */
275 if (__builtin_expect (flags & GCONV_AVOID_NOCONV, 0) && fromidx == toidx)
276 return __GCONV_NULCONV;
278 /* If there are special conversions available examine them first. */
279 if (fromidx != 0 && toidx != 0
280 && __builtin_expect (from_module->extra_offset, 0) != 0)
282 /* Search through the list to see whether there is a module
283 matching the destination character set. */
284 const struct extra_entry *extra;
286 /* Note the -1. This is due to the offset added in iconvconfig.
287 See there for more explanations. */
288 extra = (const struct extra_entry *) ((char *) gconv_cache
289 + header->otherconv_offset
290 + from_module->extra_offset - 1);
291 while (extra->module_cnt != 0
292 && extra->module[extra->module_cnt - 1].outname_offset != toidx)
293 extra = (const struct extra_entry *) ((char *) extra
294 + sizeof (struct extra_entry)
295 + (extra->module_cnt
296 * sizeof (struct extra_entry_module)));
298 if (extra->module_cnt != 0)
300 /* Use the extra module. First determine how many steps. */
301 char *fromname;
302 int idx;
304 *nsteps = extra->module_cnt;
305 *handle = result =
306 (struct __gconv_step *) malloc (extra->module_cnt
307 * sizeof (struct __gconv_step));
308 if (result == NULL)
309 return __GCONV_NOMEM;
311 fromname = (char *) strtab + from_module->canonname_offset;
312 idx = 0;
315 result[idx].__from_name = fromname;
316 fromname = result[idx].__to_name =
317 (char *) strtab + modtab[extra->module[idx].outname_offset].canonname_offset;
319 result[idx].__counter = 1;
320 result[idx].__data = NULL;
322 #ifndef STATIC_GCONV
323 if (strtab[extra->module[idx].dir_offset] != '\0')
325 /* Load the module, return handle for it. */
326 int res;
328 res = find_module (strtab + extra->module[idx].dir_offset,
329 strtab + extra->module[idx].name_offset,
330 &result[idx]);
331 if (__builtin_expect (res, __GCONV_OK) != __GCONV_OK)
333 /* Something went wrong. */
334 free (result);
335 goto try_internal;
338 else
339 #endif
340 /* It's a builtin transformation. */
341 __gconv_get_builtin_trans (strtab
342 + extra->module[idx].name_offset,
343 &result[idx]);
346 while (++idx < extra->module_cnt);
348 return __GCONV_OK;
352 try_internal:
353 /* See whether we can convert via the INTERNAL charset. */
354 if ((fromidx != 0 && __builtin_expect (from_module->fromname_offset, 1) == 0)
355 || (toidx != 0 && __builtin_expect (to_module->toname_offset, 1) == 0)
356 || (fromidx == 0 && toidx == 0))
357 /* Not possible. Nothing we can do. */
358 return __GCONV_NOCONV;
360 /* We will use up to two modules. Always allocate room for two. */
361 result = (struct __gconv_step *) malloc (2 * sizeof (struct __gconv_step));
362 if (result == NULL)
363 return __GCONV_NOMEM;
365 *handle = result;
366 *nsteps = 0;
368 /* Generate data structure for conversion to INTERNAL. */
369 if (fromidx != 0)
371 result[0].__from_name = (char *) strtab + from_module->canonname_offset;
372 result[0].__to_name = (char *) "INTERNAL";
374 result[0].__counter = 1;
375 result[0].__data = NULL;
377 #ifndef STATIC_GCONV
378 if (strtab[from_module->todir_offset] != '\0')
380 /* Load the module, return handle for it. */
381 int res = find_module (strtab + from_module->todir_offset,
382 strtab + from_module->toname_offset,
383 &result[0]);
384 if (__builtin_expect (res, __GCONV_OK) != __GCONV_OK)
386 /* Something went wrong. */
387 free (result);
388 return res;
391 else
392 #endif
393 /* It's a builtin transformation. */
394 __gconv_get_builtin_trans (strtab + from_module->toname_offset,
395 &result[0]);
397 ++*nsteps;
400 /* Generate data structure for conversion from INTERNAL. */
401 if (toidx != 0)
403 int idx = *nsteps;
405 result[idx].__from_name = (char *) "INTERNAL";
406 result[idx].__to_name = (char *) strtab + to_module->canonname_offset;
408 result[idx].__counter = 1;
409 result[idx].__data = NULL;
411 #ifndef STATIC_GCONV
412 if (strtab[to_module->fromdir_offset] != '\0')
414 /* Load the module, return handle for it. */
415 int res = find_module (strtab + to_module->fromdir_offset,
416 strtab + to_module->fromname_offset,
417 &result[idx]);
418 if (__builtin_expect (res, __GCONV_OK) != __GCONV_OK)
420 /* Something went wrong. */
421 if (idx != 0)
422 __gconv_release_step (&result[0]);
423 free (result);
424 return res;
427 else
428 #endif
429 /* It's a builtin transformation. */
430 __gconv_get_builtin_trans (strtab + to_module->fromname_offset,
431 &result[idx]);
433 ++*nsteps;
436 return __GCONV_OK;
440 /* Free memory allocated for the transformation record. */
441 void
442 __gconv_release_cache (struct __gconv_step *steps, size_t nsteps)
444 if (gconv_cache != NULL)
445 /* The only thing we have to deallocate is the record with the
446 steps. */
447 free (steps);
451 /* Free all resources if necessary. */
452 void
453 __gconv_cache_freemem (void)
455 if (cache_malloced)
456 free (gconv_cache);
457 #ifdef _POSIX_MAPPED_FILES
458 else if (gconv_cache != NULL)
459 __munmap (gconv_cache, cache_size);
460 #endif