(root)/
glibc-2.38/
iconv/
gconv_cache.c
       1  /* Cache handling for iconv modules.
       2     Copyright (C) 2001-2023 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  }