packaging: Enable memcpy in sysdeps/arm/memcpy.S for ARM
[platform/upstream/glibc.git] / iconv / gconv_cache.c
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
35 static void *gconv_cache;
36 static size_t cache_size;
37 static int cache_malloced;
38
39
40 void *
41 __gconv_get_cache (void)
42 {
43   return gconv_cache;
44 }
45
46
47 int
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 (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 (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 (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
143 static int
144 find_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);
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
181 static int
182 find_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 (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
221 int
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 (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);
235
236   return 0;
237 }
238
239
240 int
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 (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];
267
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];
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 (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 (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)
332                     {
333                       /* Something went wrong.  */
334                       free (result);
335                       goto try_internal;
336                     }
337                 }
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]);
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 (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 (strtab + from_module->todir_offset,
382                                  strtab + from_module->toname_offset,
383                                  &result[0]);
384           if (__builtin_expect (res, __GCONV_OK) != __GCONV_OK)
385             {
386               /* Something went wrong.  */
387               free (result);
388               return res;
389             }
390         }
391       else
392 #endif
393         /* It's a builtin transformation.  */
394         __gconv_get_builtin_trans (strtab + from_module->toname_offset,
395                                    &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 (strtab + to_module->fromdir_offset,
416                                  strtab + to_module->fromname_offset,
417                                  &result[idx]);
418           if (__builtin_expect (res, __GCONV_OK) != __GCONV_OK)
419             {
420               /* Something went wrong.  */
421               if (idx != 0)
422                 __gconv_release_step (&result[0]);
423               free (result);
424               return res;
425             }
426         }
427       else
428 #endif
429         /* It's a builtin transformation.  */
430         __gconv_get_builtin_trans (strtab + to_module->fromname_offset,
431                                    &result[idx]);
432
433       ++*nsteps;
434     }
435
436   return __GCONV_OK;
437 }
438
439
440 /* Free memory allocated for the transformation record.  */
441 void
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 (steps);
448 }
449
450
451 /* Free all resources if necessary.  */
452 void
453 __gconv_cache_freemem (void)
454 {
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
461 }