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.
4
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.
9
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.
14
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/>. */
18
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>
27
28#include <gconv_int.h>
29#include <iconvconfig.h>
30#include <not-cancel.h>
31#include <pointer_guard.h>
32
33#include "../intl/hash-string.h"
34
35static void *gconv_cache;
36static size_t cache_size;
37static int cache_malloced;
38
39
40void *
41__gconv_get_cache (void)
42{
43 return gconv_cache;
44}
45
46
47int
48__gconv_load_cache (void)
49{
50 int fd;
51 struct __stat64_t64 st;
52 struct gconvcache_header *header;
53
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;
59
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;
65
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))
71 {
72 close_and_exit:
73 __close_nocancel_nostatus (fd);
74 return -1;
75 }
76
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
83 {
84 size_t already_read;
85
86 gconv_cache = malloc (size: cache_size);
87 if (gconv_cache == NULL)
88 goto close_and_exit;
89
90 already_read = 0;
91 do
92 {
93 ssize_t n = __read (fd, (char *) gconv_cache + already_read,
94 cache_size - already_read);
95 if (__builtin_expect (n, 0) == -1)
96 {
97 free (ptr: gconv_cache);
98 gconv_cache = NULL;
99 goto close_and_exit;
100 }
101
102 already_read += n;
103 }
104 while (already_read < cache_size);
105
106 cache_malloced = 1;
107 }
108
109 /* We don't need the file descriptor anymore. */
110 __close_nocancel_nostatus (fd);
111
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))
123 {
124 if (cache_malloced)
125 {
126 free (ptr: gconv_cache);
127 cache_malloced = 0;
128 }
129#ifdef _POSIX_MAPPED_FILES
130 else
131 __munmap (gconv_cache, cache_size);
132#endif
133 gconv_cache = NULL;
134
135 return -1;
136 }
137
138 /* That worked. */
139 return 0;
140}
141
142
143static int
144find_module_idx (const char *str, size_t *idxp)
145{
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;
153
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);
158
159 hval = __hash_string (str_param: str);
160 idx = hval % header->hash_size;
161 hval2 = 1 + hval % (header->hash_size - 2);
162
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)
167 {
168 *idxp = hashtab[idx].module_idx;
169 return 0;
170 }
171 else
172 if ((idx += hval2) >= header->hash_size)
173 idx -= header->hash_size;
174
175 /* Nothing found. */
176 return -1;
177}
178
179
180#ifndef STATIC_GCONV
181static int
182find_module (const char *directory, const char *filename,
183 struct __gconv_step *result)
184{
185 size_t dirlen = strlen (directory);
186 size_t fnamelen = strlen (filename) + 1;
187 char fullname[dirlen + fnamelen];
188 int status = __GCONV_NOCONV;
189
190 memcpy (__mempcpy (fullname, directory, dirlen), filename, fnamelen);
191
192 result->__shlib_handle = __gconv_find_shlib (name: fullname);
193 if (result->__shlib_handle != NULL)
194 {
195 status = __GCONV_OK;
196
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;
201
202 /* These settings can be overridden by the init function. */
203 result->__btowc_fct = NULL;
204 result->__data = NULL;
205
206 /* Call the init function. */
207 __gconv_init_fct init_fct = result->__init_fct;
208 PTR_DEMANGLE (init_fct);
209 if (init_fct != NULL)
210 {
211 status = DL_CALL_FCT (init_fct, (result));
212 PTR_MANGLE (result->__btowc_fct);
213 }
214 }
215
216 return status;
217}
218#endif
219
220
221int
222__gconv_compare_alias_cache (const char *name1, const char *name2, int *result)
223{
224 size_t name1_idx;
225 size_t name2_idx;
226
227 if (gconv_cache == NULL)
228 return -1;
229
230 if (find_module_idx (str: name1, idxp: &name1_idx) != 0
231 || find_module_idx (str: name2, idxp: &name2_idx) != 0)
232 *result = strcmp (name1, name2);
233 else
234 *result = (int) (name1_idx - name2_idx);
235
236 return 0;
237}
238
239
240int
241__gconv_lookup_cache (const char *toset, const char *fromset,
242 struct __gconv_step **handle, size_t *nsteps, int flags)
243{
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;
252
253 if (gconv_cache == NULL)
254 /* We have no cache available. */
255 return __GCONV_NODB;
256
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);
261
262 if (find_module_idx (str: fromset, idxp: &fromidx) != 0
263 || (header->module_offset + (fromidx + 1) * sizeof (struct module_entry)
264 > cache_size))
265 return __GCONV_NOCONV;
266 from_module = &modtab[fromidx];
267
268 if (find_module_idx (str: toset, idxp: &toidx) != 0
269 || (header->module_offset + (toidx + 1) * sizeof (struct module_entry)
270 > cache_size))
271 return __GCONV_NOCONV;
272 to_module = &modtab[toidx];
273
274 /* Avoid copy-only transformations if the user requests. */
275 if (__builtin_expect (flags & GCONV_AVOID_NOCONV, 0) && fromidx == toidx)
276 return __GCONV_NULCONV;
277
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)
281 {
282 /* Search through the list to see whether there is a module
283 matching the destination character set. */
284 const struct extra_entry *extra;
285
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)));
297
298 if (extra->module_cnt != 0)
299 {
300 /* Use the extra module. First determine how many steps. */
301 char *fromname;
302 int idx;
303
304 *nsteps = extra->module_cnt;
305 *handle = result =
306 (struct __gconv_step *) malloc (size: extra->module_cnt
307 * sizeof (struct __gconv_step));
308 if (result == NULL)
309 return __GCONV_NOMEM;
310
311 fromname = (char *) strtab + from_module->canonname_offset;
312 idx = 0;
313 do
314 {
315 result[idx].__from_name = fromname;
316 fromname = result[idx].__to_name =
317 (char *) strtab + modtab[extra->module[idx].outname_offset].canonname_offset;
318
319 result[idx].__counter = 1;
320 result[idx].__data = NULL;
321
322#ifndef STATIC_GCONV
323 if (strtab[extra->module[idx].dir_offset] != '\0')
324 {
325 /* Load the module, return handle for it. */
326 int res;
327
328 res = find_module (directory: strtab + extra->module[idx].dir_offset,
329 filename: strtab + extra->module[idx].name_offset,
330 result: &result[idx]);
331 if (__builtin_expect (res, __GCONV_OK) != __GCONV_OK)
332 {
333 /* Something went wrong. */
334 free (ptr: result);
335 goto try_internal;
336 }
337 }
338 else
339#endif
340 /* It's a builtin transformation. */
341 __gconv_get_builtin_trans (name: strtab
342 + extra->module[idx].name_offset,
343 step: &result[idx]);
344
345 }
346 while (++idx < extra->module_cnt);
347
348 return __GCONV_OK;
349 }
350 }
351
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;
359
360 /* We will use up to two modules. Always allocate room for two. */
361 result = (struct __gconv_step *) malloc (size: 2 * sizeof (struct __gconv_step));
362 if (result == NULL)
363 return __GCONV_NOMEM;
364
365 *handle = result;
366 *nsteps = 0;
367
368 /* Generate data structure for conversion to INTERNAL. */
369 if (fromidx != 0)
370 {
371 result[0].__from_name = (char *) strtab + from_module->canonname_offset;
372 result[0].__to_name = (char *) "INTERNAL";
373
374 result[0].__counter = 1;
375 result[0].__data = NULL;
376
377#ifndef STATIC_GCONV
378 if (strtab[from_module->todir_offset] != '\0')
379 {
380 /* Load the module, return handle for it. */
381 int res = find_module (directory: strtab + from_module->todir_offset,
382 filename: strtab + from_module->toname_offset,
383 result: &result[0]);
384 if (__builtin_expect (res, __GCONV_OK) != __GCONV_OK)
385 {
386 /* Something went wrong. */
387 free (ptr: result);
388 return res;
389 }
390 }
391 else
392#endif
393 /* It's a builtin transformation. */
394 __gconv_get_builtin_trans (name: strtab + from_module->toname_offset,
395 step: &result[0]);
396
397 ++*nsteps;
398 }
399
400 /* Generate data structure for conversion from INTERNAL. */
401 if (toidx != 0)
402 {
403 int idx = *nsteps;
404
405 result[idx].__from_name = (char *) "INTERNAL";
406 result[idx].__to_name = (char *) strtab + to_module->canonname_offset;
407
408 result[idx].__counter = 1;
409 result[idx].__data = NULL;
410
411#ifndef STATIC_GCONV
412 if (strtab[to_module->fromdir_offset] != '\0')
413 {
414 /* Load the module, return handle for it. */
415 int res = find_module (directory: strtab + to_module->fromdir_offset,
416 filename: strtab + to_module->fromname_offset,
417 result: &result[idx]);
418 if (__builtin_expect (res, __GCONV_OK) != __GCONV_OK)
419 {
420 /* Something went wrong. */
421 if (idx != 0)
422 __gconv_release_step (step: &result[0]);
423 free (ptr: result);
424 return res;
425 }
426 }
427 else
428#endif
429 /* It's a builtin transformation. */
430 __gconv_get_builtin_trans (name: strtab + to_module->fromname_offset,
431 step: &result[idx]);
432
433 ++*nsteps;
434 }
435
436 return __GCONV_OK;
437}
438
439
440/* Free memory allocated for the transformation record. */
441void
442__gconv_release_cache (struct __gconv_step *steps, size_t nsteps)
443{
444 if (gconv_cache != NULL)
445 /* The only thing we have to deallocate is the record with the
446 steps. */
447 free (ptr: steps);
448}
449
450
451/* Free all resources if necessary. */
452void
453__gconv_cache_freemem (void)
454{
455 if (cache_malloced)
456 free (ptr: gconv_cache);
457#ifdef _POSIX_MAPPED_FILES
458 else if (gconv_cache != NULL)
459 __munmap (gconv_cache, cache_size);
460#endif
461}
462

source code of glibc/iconv/gconv_cache.c