glibc/iconv/gconv_cache.c
Siddhesh Poyarekar 30891f35fa Remove "Contributed by" lines
We stopped adding "Contributed by" or similar lines in sources in 2012
in favour of git logs and keeping the Contributors section of the
glibc manual up to date.  Removing these lines makes the license
header a bit more consistent across files and also removes the
possibility of error in attribution when license blocks or files are
copied across since the contributed-by lines don't actually reflect
reality in those cases.

Move all "Contributed by" and similar lines (Written by, Test by,
etc.) into a new file CONTRIBUTED-BY to retain record of these
contributions.  These contributors are also mentioned in
manual/contrib.texi, so we just maintain this additional record as a
courtesy to the earlier developers.

The following scripts were used to filter a list of files to edit in
place and to clean up the CONTRIBUTED-BY file respectively.  These
were not added to the glibc sources because they're not expected to be
of any use in future given that this is a one time task:

https://gist.github.com/siddhesh/b5ecac94eabfd72ed2916d6d8157e7dc
https://gist.github.com/siddhesh/15ea1f5e435ace9774f485030695ee02

Reviewed-by: Carlos O'Donell <carlos@redhat.com>
2021-09-03 22:06:44 +05:30

465 lines
12 KiB
C

/* Cache handling for iconv modules.
Copyright (C) 2001-2021 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <gconv_int.h>
#include <iconvconfig.h>
#include <not-cancel.h>
#include "../intl/hash-string.h"
static void *gconv_cache;
static size_t cache_size;
static int cache_malloced;
void *
__gconv_get_cache (void)
{
return gconv_cache;
}
int
__gconv_load_cache (void)
{
int fd;
struct __stat64_t64 st;
struct gconvcache_header *header;
/* We cannot use the cache if the GCONV_PATH environment variable is
set. */
__gconv_path_envvar = getenv ("GCONV_PATH");
if (__gconv_path_envvar != NULL)
return -1;
/* See whether the cache file exists. */
fd = __open_nocancel (GCONV_MODULES_CACHE, O_RDONLY, 0);
if (__builtin_expect (fd, 0) == -1)
/* Not available. */
return -1;
/* Get information about the file. */
if (__glibc_unlikely (__fstat64_time64 (fd, &st) < 0)
/* We do not have to start looking at the file if it cannot contain
at least the cache header. */
|| (size_t) st.st_size < sizeof (struct gconvcache_header))
{
close_and_exit:
__close_nocancel_nostatus (fd);
return -1;
}
/* Make the file content available. */
cache_size = st.st_size;
#ifdef _POSIX_MAPPED_FILES
gconv_cache = __mmap (NULL, cache_size, PROT_READ, MAP_SHARED, fd, 0);
if (__glibc_unlikely (gconv_cache == MAP_FAILED))
#endif
{
size_t already_read;
gconv_cache = malloc (cache_size);
if (gconv_cache == NULL)
goto close_and_exit;
already_read = 0;
do
{
ssize_t n = __read (fd, (char *) gconv_cache + already_read,
cache_size - already_read);
if (__builtin_expect (n, 0) == -1)
{
free (gconv_cache);
gconv_cache = NULL;
goto close_and_exit;
}
already_read += n;
}
while (already_read < cache_size);
cache_malloced = 1;
}
/* We don't need the file descriptor anymore. */
__close_nocancel_nostatus (fd);
/* Check the consistency. */
header = (struct gconvcache_header *) gconv_cache;
if (__builtin_expect (header->magic, GCONVCACHE_MAGIC) != GCONVCACHE_MAGIC
|| __builtin_expect (header->string_offset >= cache_size, 0)
|| __builtin_expect (header->hash_offset >= cache_size, 0)
|| __builtin_expect (header->hash_size == 0, 0)
|| __builtin_expect ((header->hash_offset
+ header->hash_size * sizeof (struct hash_entry))
> cache_size, 0)
|| __builtin_expect (header->module_offset >= cache_size, 0)
|| __builtin_expect (header->otherconv_offset > cache_size, 0))
{
if (cache_malloced)
{
free (gconv_cache);
cache_malloced = 0;
}
#ifdef _POSIX_MAPPED_FILES
else
__munmap (gconv_cache, cache_size);
#endif
gconv_cache = NULL;
return -1;
}
/* That worked. */
return 0;
}
static int
find_module_idx (const char *str, size_t *idxp)
{
unsigned int idx;
unsigned int hval;
unsigned int hval2;
const struct gconvcache_header *header;
const char *strtab;
const struct hash_entry *hashtab;
unsigned int limit;
header = (const struct gconvcache_header *) gconv_cache;
strtab = (char *) gconv_cache + header->string_offset;
hashtab = (struct hash_entry *) ((char *) gconv_cache
+ header->hash_offset);
hval = __hash_string (str);
idx = hval % header->hash_size;
hval2 = 1 + hval % (header->hash_size - 2);
limit = cache_size - header->string_offset;
while (hashtab[idx].string_offset != 0)
if (hashtab[idx].string_offset < limit
&& strcmp (str, strtab + hashtab[idx].string_offset) == 0)
{
*idxp = hashtab[idx].module_idx;
return 0;
}
else
if ((idx += hval2) >= header->hash_size)
idx -= header->hash_size;
/* Nothing found. */
return -1;
}
#ifndef STATIC_GCONV
static int
find_module (const char *directory, const char *filename,
struct __gconv_step *result)
{
size_t dirlen = strlen (directory);
size_t fnamelen = strlen (filename) + 1;
char fullname[dirlen + fnamelen];
int status = __GCONV_NOCONV;
memcpy (__mempcpy (fullname, directory, dirlen), filename, fnamelen);
result->__shlib_handle = __gconv_find_shlib (fullname);
if (result->__shlib_handle != NULL)
{
status = __GCONV_OK;
result->__modname = NULL;
result->__fct = result->__shlib_handle->fct;
result->__init_fct = result->__shlib_handle->init_fct;
result->__end_fct = result->__shlib_handle->end_fct;
/* These settings can be overridden by the init function. */
result->__btowc_fct = NULL;
result->__data = NULL;
/* Call the init function. */
__gconv_init_fct init_fct = result->__init_fct;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (init_fct);
#endif
if (init_fct != NULL)
{
status = DL_CALL_FCT (init_fct, (result));
#ifdef PTR_MANGLE
PTR_MANGLE (result->__btowc_fct);
#endif
}
}
return status;
}
#endif
int
__gconv_compare_alias_cache (const char *name1, const char *name2, int *result)
{
size_t name1_idx;
size_t name2_idx;
if (gconv_cache == NULL)
return -1;
if (find_module_idx (name1, &name1_idx) != 0
|| find_module_idx (name2, &name2_idx) != 0)
*result = strcmp (name1, name2);
else
*result = (int) (name1_idx - name2_idx);
return 0;
}
int
__gconv_lookup_cache (const char *toset, const char *fromset,
struct __gconv_step **handle, size_t *nsteps, int flags)
{
const struct gconvcache_header *header;
const char *strtab;
size_t fromidx;
size_t toidx;
const struct module_entry *modtab;
const struct module_entry *from_module;
const struct module_entry *to_module;
struct __gconv_step *result;
if (gconv_cache == NULL)
/* We have no cache available. */
return __GCONV_NODB;
header = (const struct gconvcache_header *) gconv_cache;
strtab = (char *) gconv_cache + header->string_offset;
modtab = (const struct module_entry *) ((char *) gconv_cache
+ header->module_offset);
if (find_module_idx (fromset, &fromidx) != 0
|| (header->module_offset + (fromidx + 1) * sizeof (struct module_entry)
> cache_size))
return __GCONV_NOCONV;
from_module = &modtab[fromidx];
if (find_module_idx (toset, &toidx) != 0
|| (header->module_offset + (toidx + 1) * sizeof (struct module_entry)
> cache_size))
return __GCONV_NOCONV;
to_module = &modtab[toidx];
/* Avoid copy-only transformations if the user requests. */
if (__builtin_expect (flags & GCONV_AVOID_NOCONV, 0) && fromidx == toidx)
return __GCONV_NULCONV;
/* If there are special conversions available examine them first. */
if (fromidx != 0 && toidx != 0
&& __builtin_expect (from_module->extra_offset, 0) != 0)
{
/* Search through the list to see whether there is a module
matching the destination character set. */
const struct extra_entry *extra;
/* Note the -1. This is due to the offset added in iconvconfig.
See there for more explanations. */
extra = (const struct extra_entry *) ((char *) gconv_cache
+ header->otherconv_offset
+ from_module->extra_offset - 1);
while (extra->module_cnt != 0
&& extra->module[extra->module_cnt - 1].outname_offset != toidx)
extra = (const struct extra_entry *) ((char *) extra
+ sizeof (struct extra_entry)
+ (extra->module_cnt
* sizeof (struct extra_entry_module)));
if (extra->module_cnt != 0)
{
/* Use the extra module. First determine how many steps. */
char *fromname;
int idx;
*nsteps = extra->module_cnt;
*handle = result =
(struct __gconv_step *) malloc (extra->module_cnt
* sizeof (struct __gconv_step));
if (result == NULL)
return __GCONV_NOMEM;
fromname = (char *) strtab + from_module->canonname_offset;
idx = 0;
do
{
result[idx].__from_name = fromname;
fromname = result[idx].__to_name =
(char *) strtab + modtab[extra->module[idx].outname_offset].canonname_offset;
result[idx].__counter = 1;
result[idx].__data = NULL;
#ifndef STATIC_GCONV
if (strtab[extra->module[idx].dir_offset] != '\0')
{
/* Load the module, return handle for it. */
int res;
res = find_module (strtab + extra->module[idx].dir_offset,
strtab + extra->module[idx].name_offset,
&result[idx]);
if (__builtin_expect (res, __GCONV_OK) != __GCONV_OK)
{
/* Something went wrong. */
free (result);
goto try_internal;
}
}
else
#endif
/* It's a builtin transformation. */
__gconv_get_builtin_trans (strtab
+ extra->module[idx].name_offset,
&result[idx]);
}
while (++idx < extra->module_cnt);
return __GCONV_OK;
}
}
try_internal:
/* See whether we can convert via the INTERNAL charset. */
if ((fromidx != 0 && __builtin_expect (from_module->fromname_offset, 1) == 0)
|| (toidx != 0 && __builtin_expect (to_module->toname_offset, 1) == 0)
|| (fromidx == 0 && toidx == 0))
/* Not possible. Nothing we can do. */
return __GCONV_NOCONV;
/* We will use up to two modules. Always allocate room for two. */
result = (struct __gconv_step *) malloc (2 * sizeof (struct __gconv_step));
if (result == NULL)
return __GCONV_NOMEM;
*handle = result;
*nsteps = 0;
/* Generate data structure for conversion to INTERNAL. */
if (fromidx != 0)
{
result[0].__from_name = (char *) strtab + from_module->canonname_offset;
result[0].__to_name = (char *) "INTERNAL";
result[0].__counter = 1;
result[0].__data = NULL;
#ifndef STATIC_GCONV
if (strtab[from_module->todir_offset] != '\0')
{
/* Load the module, return handle for it. */
int res = find_module (strtab + from_module->todir_offset,
strtab + from_module->toname_offset,
&result[0]);
if (__builtin_expect (res, __GCONV_OK) != __GCONV_OK)
{
/* Something went wrong. */
free (result);
return res;
}
}
else
#endif
/* It's a builtin transformation. */
__gconv_get_builtin_trans (strtab + from_module->toname_offset,
&result[0]);
++*nsteps;
}
/* Generate data structure for conversion from INTERNAL. */
if (toidx != 0)
{
int idx = *nsteps;
result[idx].__from_name = (char *) "INTERNAL";
result[idx].__to_name = (char *) strtab + to_module->canonname_offset;
result[idx].__counter = 1;
result[idx].__data = NULL;
#ifndef STATIC_GCONV
if (strtab[to_module->fromdir_offset] != '\0')
{
/* Load the module, return handle for it. */
int res = find_module (strtab + to_module->fromdir_offset,
strtab + to_module->fromname_offset,
&result[idx]);
if (__builtin_expect (res, __GCONV_OK) != __GCONV_OK)
{
/* Something went wrong. */
if (idx != 0)
__gconv_release_step (&result[0]);
free (result);
return res;
}
}
else
#endif
/* It's a builtin transformation. */
__gconv_get_builtin_trans (strtab + to_module->fromname_offset,
&result[idx]);
++*nsteps;
}
return __GCONV_OK;
}
/* Free memory allocated for the transformation record. */
void
__gconv_release_cache (struct __gconv_step *steps, size_t nsteps)
{
if (gconv_cache != NULL)
/* The only thing we have to deallocate is the record with the
steps. */
free (steps);
}
/* Free all resources if necessary. */
libc_freeres_fn (free_mem)
{
if (cache_malloced)
free (gconv_cache);
#ifdef _POSIX_MAPPED_FILES
else if (gconv_cache != NULL)
__munmap (gconv_cache, cache_size);
#endif
}