1 /* Cache handling for iconv modules.
2    Copyright (C) 2001-2022 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 
32 #include "../intl/hash-string.h"
33 
34 static void *gconv_cache;
35 static size_t cache_size;
36 static int cache_malloced;
37 
38 
39 void *
__gconv_get_cache(void)40 __gconv_get_cache (void)
41 {
42   return gconv_cache;
43 }
44 
45 
46 int
__gconv_load_cache(void)47 __gconv_load_cache (void)
48 {
49   int fd;
50   struct __stat64_t64 st;
51   struct gconvcache_header *header;
52 
53   /* We cannot use the cache if the GCONV_PATH environment variable is
54      set.  */
55   __gconv_path_envvar = getenv ("GCONV_PATH");
56   if (__gconv_path_envvar != NULL)
57     return -1;
58 
59   /* See whether the cache file exists.  */
60   fd = __open_nocancel (GCONV_MODULES_CACHE, O_RDONLY, 0);
61   if (__builtin_expect (fd, 0) == -1)
62     /* Not available.  */
63     return -1;
64 
65   /* Get information about the file.  */
66   if (__glibc_unlikely (__fstat64_time64 (fd, &st) < 0)
67       /* We do not have to start looking at the file if it cannot contain
68 	 at least the cache header.  */
69       || (size_t) st.st_size < sizeof (struct gconvcache_header))
70     {
71     close_and_exit:
72       __close_nocancel_nostatus (fd);
73       return -1;
74     }
75 
76   /* Make the file content available.  */
77   cache_size = st.st_size;
78 #ifdef _POSIX_MAPPED_FILES
79   gconv_cache = __mmap (NULL, cache_size, PROT_READ, MAP_SHARED, fd, 0);
80   if (__glibc_unlikely (gconv_cache == MAP_FAILED))
81 #endif
82     {
83       size_t already_read;
84 
85       gconv_cache = malloc (cache_size);
86       if (gconv_cache == NULL)
87 	goto close_and_exit;
88 
89       already_read = 0;
90       do
91 	{
92 	  ssize_t n = __read (fd, (char *) gconv_cache + already_read,
93 			      cache_size - already_read);
94 	  if (__builtin_expect (n, 0) == -1)
95 	    {
96 	      free (gconv_cache);
97 	      gconv_cache = NULL;
98 	      goto close_and_exit;
99 	    }
100 
101 	  already_read += n;
102 	}
103       while (already_read < cache_size);
104 
105       cache_malloced = 1;
106     }
107 
108   /* We don't need the file descriptor anymore.  */
109   __close_nocancel_nostatus (fd);
110 
111   /* Check the consistency.  */
112   header = (struct gconvcache_header *) gconv_cache;
113   if (__builtin_expect (header->magic, GCONVCACHE_MAGIC) != GCONVCACHE_MAGIC
114       || __builtin_expect (header->string_offset >= cache_size, 0)
115       || __builtin_expect (header->hash_offset >= cache_size, 0)
116       || __builtin_expect (header->hash_size == 0, 0)
117       || __builtin_expect ((header->hash_offset
118 			    + header->hash_size * sizeof (struct hash_entry))
119 			   > cache_size, 0)
120       || __builtin_expect (header->module_offset >= cache_size, 0)
121       || __builtin_expect (header->otherconv_offset > cache_size, 0))
122     {
123       if (cache_malloced)
124 	{
125 	  free (gconv_cache);
126 	  cache_malloced = 0;
127 	}
128 #ifdef _POSIX_MAPPED_FILES
129       else
130 	__munmap (gconv_cache, cache_size);
131 #endif
132       gconv_cache = NULL;
133 
134       return -1;
135     }
136 
137   /* That worked.  */
138   return 0;
139 }
140 
141 
142 static int
find_module_idx(const char * str,size_t * idxp)143 find_module_idx (const char *str, size_t *idxp)
144 {
145   unsigned int idx;
146   unsigned int hval;
147   unsigned int hval2;
148   const struct gconvcache_header *header;
149   const char *strtab;
150   const struct hash_entry *hashtab;
151   unsigned int limit;
152 
153   header = (const struct gconvcache_header *) gconv_cache;
154   strtab = (char *) gconv_cache + header->string_offset;
155   hashtab = (struct hash_entry *) ((char *) gconv_cache
156 				   + header->hash_offset);
157 
158   hval = __hash_string (str);
159   idx = hval % header->hash_size;
160   hval2 = 1 + hval % (header->hash_size - 2);
161 
162   limit = cache_size - header->string_offset;
163   while (hashtab[idx].string_offset != 0)
164     if (hashtab[idx].string_offset < limit
165 	&& strcmp (str, strtab + hashtab[idx].string_offset) == 0)
166       {
167 	*idxp = hashtab[idx].module_idx;
168 	return 0;
169       }
170     else
171       if ((idx += hval2) >= header->hash_size)
172 	idx -= header->hash_size;
173 
174   /* Nothing found.  */
175   return -1;
176 }
177 
178 
179 #ifndef STATIC_GCONV
180 static int
find_module(const char * directory,const char * filename,struct __gconv_step * result)181 find_module (const char *directory, const char *filename,
182 	     struct __gconv_step *result)
183 {
184   size_t dirlen = strlen (directory);
185   size_t fnamelen = strlen (filename) + 1;
186   char fullname[dirlen + fnamelen];
187   int status = __GCONV_NOCONV;
188 
189   memcpy (__mempcpy (fullname, directory, dirlen), filename, fnamelen);
190 
191   result->__shlib_handle = __gconv_find_shlib (fullname);
192   if (result->__shlib_handle != NULL)
193     {
194       status = __GCONV_OK;
195 
196       result->__modname = NULL;
197       result->__fct = result->__shlib_handle->fct;
198       result->__init_fct = result->__shlib_handle->init_fct;
199       result->__end_fct = result->__shlib_handle->end_fct;
200 
201       /* These settings can be overridden by the init function.  */
202       result->__btowc_fct = NULL;
203       result->__data = NULL;
204 
205       /* Call the init function.  */
206       __gconv_init_fct init_fct = result->__init_fct;
207 #ifdef PTR_DEMANGLE
208       PTR_DEMANGLE (init_fct);
209 #endif
210       if (init_fct != NULL)
211 	{
212 	  status = DL_CALL_FCT (init_fct, (result));
213 
214 #ifdef PTR_MANGLE
215 	  PTR_MANGLE (result->__btowc_fct);
216 #endif
217 	}
218     }
219 
220   return status;
221 }
222 #endif
223 
224 
225 int
__gconv_compare_alias_cache(const char * name1,const char * name2,int * result)226 __gconv_compare_alias_cache (const char *name1, const char *name2, int *result)
227 {
228   size_t name1_idx;
229   size_t name2_idx;
230 
231   if (gconv_cache == NULL)
232     return -1;
233 
234   if (find_module_idx (name1, &name1_idx) != 0
235       || find_module_idx (name2, &name2_idx) != 0)
236     *result = strcmp (name1, name2);
237   else
238     *result = (int) (name1_idx - name2_idx);
239 
240   return 0;
241 }
242 
243 
244 int
__gconv_lookup_cache(const char * toset,const char * fromset,struct __gconv_step ** handle,size_t * nsteps,int flags)245 __gconv_lookup_cache (const char *toset, const char *fromset,
246 		      struct __gconv_step **handle, size_t *nsteps, int flags)
247 {
248   const struct gconvcache_header *header;
249   const char *strtab;
250   size_t fromidx;
251   size_t toidx;
252   const struct module_entry *modtab;
253   const struct module_entry *from_module;
254   const struct module_entry *to_module;
255   struct __gconv_step *result;
256 
257   if (gconv_cache == NULL)
258     /* We have no cache available.  */
259     return __GCONV_NODB;
260 
261   header = (const struct gconvcache_header *) gconv_cache;
262   strtab = (char *) gconv_cache + header->string_offset;
263   modtab = (const struct module_entry *) ((char *) gconv_cache
264 					  + header->module_offset);
265 
266   if (find_module_idx (fromset, &fromidx) != 0
267       || (header->module_offset + (fromidx + 1) * sizeof (struct module_entry)
268 	  > cache_size))
269     return __GCONV_NOCONV;
270   from_module = &modtab[fromidx];
271 
272   if (find_module_idx (toset, &toidx) != 0
273       || (header->module_offset + (toidx + 1) * sizeof (struct module_entry)
274 	  > cache_size))
275     return __GCONV_NOCONV;
276   to_module = &modtab[toidx];
277 
278   /* Avoid copy-only transformations if the user requests.   */
279   if (__builtin_expect (flags & GCONV_AVOID_NOCONV, 0) && fromidx == toidx)
280     return __GCONV_NULCONV;
281 
282   /* If there are special conversions available examine them first.  */
283   if (fromidx != 0 && toidx != 0
284       && __builtin_expect (from_module->extra_offset, 0) != 0)
285     {
286       /* Search through the list to see whether there is a module
287 	 matching the destination character set.  */
288       const struct extra_entry *extra;
289 
290       /* Note the -1.  This is due to the offset added in iconvconfig.
291 	 See there for more explanations.  */
292       extra = (const struct extra_entry *) ((char *) gconv_cache
293 					    + header->otherconv_offset
294 					    + from_module->extra_offset - 1);
295       while (extra->module_cnt != 0
296 	     && extra->module[extra->module_cnt - 1].outname_offset != toidx)
297 	extra = (const struct extra_entry *) ((char *) extra
298 					      + sizeof (struct extra_entry)
299 					      + (extra->module_cnt
300 						 * sizeof (struct extra_entry_module)));
301 
302       if (extra->module_cnt != 0)
303 	{
304 	  /* Use the extra module.  First determine how many steps.  */
305 	  char *fromname;
306 	  int idx;
307 
308 	  *nsteps = extra->module_cnt;
309 	  *handle = result =
310 	    (struct __gconv_step *) malloc (extra->module_cnt
311 					    * sizeof (struct __gconv_step));
312 	  if (result == NULL)
313 	    return __GCONV_NOMEM;
314 
315 	  fromname = (char *) strtab + from_module->canonname_offset;
316 	  idx = 0;
317 	  do
318 	    {
319 	      result[idx].__from_name = fromname;
320 	      fromname = result[idx].__to_name =
321 		(char *) strtab + modtab[extra->module[idx].outname_offset].canonname_offset;
322 
323 	      result[idx].__counter = 1;
324 	      result[idx].__data = NULL;
325 
326 #ifndef STATIC_GCONV
327 	      if (strtab[extra->module[idx].dir_offset] != '\0')
328 		{
329 		  /* Load the module, return handle for it.  */
330 		  int res;
331 
332 		  res = find_module (strtab + extra->module[idx].dir_offset,
333 				     strtab + extra->module[idx].name_offset,
334 				     &result[idx]);
335 		  if (__builtin_expect (res, __GCONV_OK) != __GCONV_OK)
336 		    {
337 		      /* Something went wrong.  */
338 		      free (result);
339 		      goto try_internal;
340 		    }
341 		}
342 	      else
343 #endif
344 		/* It's a builtin transformation.  */
345 		__gconv_get_builtin_trans (strtab
346 					   + extra->module[idx].name_offset,
347 					   &result[idx]);
348 
349 	    }
350 	  while (++idx < extra->module_cnt);
351 
352 	  return __GCONV_OK;
353 	}
354     }
355 
356  try_internal:
357   /* See whether we can convert via the INTERNAL charset.  */
358   if ((fromidx != 0 && __builtin_expect (from_module->fromname_offset, 1) == 0)
359       || (toidx != 0 && __builtin_expect (to_module->toname_offset, 1) == 0)
360       || (fromidx == 0 && toidx == 0))
361     /* Not possible.  Nothing we can do.  */
362     return __GCONV_NOCONV;
363 
364   /* We will use up to two modules.  Always allocate room for two.  */
365   result = (struct __gconv_step *) malloc (2 * sizeof (struct __gconv_step));
366   if (result == NULL)
367     return __GCONV_NOMEM;
368 
369   *handle = result;
370   *nsteps = 0;
371 
372   /* Generate data structure for conversion to INTERNAL.  */
373   if (fromidx != 0)
374     {
375       result[0].__from_name = (char *) strtab + from_module->canonname_offset;
376       result[0].__to_name = (char *) "INTERNAL";
377 
378       result[0].__counter = 1;
379       result[0].__data = NULL;
380 
381 #ifndef STATIC_GCONV
382       if (strtab[from_module->todir_offset] != '\0')
383 	{
384 	  /* Load the module, return handle for it.  */
385 	  int res = find_module (strtab + from_module->todir_offset,
386 				 strtab + from_module->toname_offset,
387 				 &result[0]);
388 	  if (__builtin_expect (res, __GCONV_OK) != __GCONV_OK)
389 	    {
390 	      /* Something went wrong.  */
391 	      free (result);
392 	      return res;
393 	    }
394 	}
395       else
396 #endif
397 	/* It's a builtin transformation.  */
398 	__gconv_get_builtin_trans (strtab + from_module->toname_offset,
399 				   &result[0]);
400 
401       ++*nsteps;
402     }
403 
404   /* Generate data structure for conversion from INTERNAL.  */
405   if (toidx != 0)
406     {
407       int idx = *nsteps;
408 
409       result[idx].__from_name = (char *) "INTERNAL";
410       result[idx].__to_name = (char *) strtab + to_module->canonname_offset;
411 
412       result[idx].__counter = 1;
413       result[idx].__data = NULL;
414 
415 #ifndef STATIC_GCONV
416       if (strtab[to_module->fromdir_offset] != '\0')
417 	{
418 	  /* Load the module, return handle for it.  */
419 	  int res = find_module (strtab + to_module->fromdir_offset,
420 				 strtab + to_module->fromname_offset,
421 				 &result[idx]);
422 	  if (__builtin_expect (res, __GCONV_OK) != __GCONV_OK)
423 	    {
424 	      /* Something went wrong.  */
425 	      if (idx != 0)
426 		__gconv_release_step (&result[0]);
427 	      free (result);
428 	      return res;
429 	    }
430 	}
431       else
432 #endif
433 	/* It's a builtin transformation.  */
434 	__gconv_get_builtin_trans (strtab + to_module->fromname_offset,
435 				   &result[idx]);
436 
437       ++*nsteps;
438     }
439 
440   return __GCONV_OK;
441 }
442 
443 
444 /* Free memory allocated for the transformation record.  */
445 void
__gconv_release_cache(struct __gconv_step * steps,size_t nsteps)446 __gconv_release_cache (struct __gconv_step *steps, size_t nsteps)
447 {
448   if (gconv_cache != NULL)
449     /* The only thing we have to deallocate is the record with the
450        steps.  */
451     free (steps);
452 }
453 
454 
455 /* Free all resources if necessary.  */
libc_freeres_fn(free_mem)456 libc_freeres_fn (free_mem)
457 {
458   if (cache_malloced)
459     free (gconv_cache);
460 #ifdef _POSIX_MAPPED_FILES
461   else if (gconv_cache != NULL)
462     __munmap (gconv_cache, cache_size);
463 #endif
464 }
465