mirror of
https://sourceware.org/git/glibc.git
synced 2024-11-08 22:30:07 +00:00
88677348b4
They are both used by __libc_freeres to free all library malloc allocated resources to help tooling like mtrace or valgrind with memory leak tracking. The current scheme uses assembly markers and linker script entries to consolidate the free routine function pointers in the RELRO segment and to be freed buffers in BSS. This patch changes it to use specific free functions for libc_freeres_ptrs buffers and call the function pointer array directly with call_function_static_weak. It allows the removal of both the internal macros and the linker script sections. Checked on x86_64-linux-gnu, i686-linux-gnu, and aarch64-linux-gnu. Reviewed-by: Carlos O'Donell <carlos@redhat.com>
462 lines
12 KiB
C
462 lines
12 KiB
C
/* Cache handling for iconv modules.
|
|
Copyright (C) 2001-2023 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 <pointer_guard.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;
|
|
PTR_DEMANGLE (init_fct);
|
|
if (init_fct != NULL)
|
|
{
|
|
status = DL_CALL_FCT (init_fct, (result));
|
|
PTR_MANGLE (result->__btowc_fct);
|
|
}
|
|
}
|
|
|
|
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. */
|
|
void
|
|
__gconv_cache_freemem (void)
|
|
{
|
|
if (cache_malloced)
|
|
free (gconv_cache);
|
|
#ifdef _POSIX_MAPPED_FILES
|
|
else if (gconv_cache != NULL)
|
|
__munmap (gconv_cache, cache_size);
|
|
#endif
|
|
}
|