diff options
Diffstat (limited to 'newlib/libc/sys/linux/iconv/gconv_conf.c')
-rw-r--r-- | newlib/libc/sys/linux/iconv/gconv_conf.c | 680 |
1 files changed, 680 insertions, 0 deletions
diff --git a/newlib/libc/sys/linux/iconv/gconv_conf.c b/newlib/libc/sys/linux/iconv/gconv_conf.c new file mode 100644 index 000000000..c714102a3 --- /dev/null +++ b/newlib/libc/sys/linux/iconv/gconv_conf.c @@ -0,0 +1,680 @@ +/* Handle configuration data. + Copyright (C) 1997,98,99,2000,2001 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@cygnus.com>, 1997. + + 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, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <locale.h> +#include <search.h> +#include <stddef.h> +#include <stdio.h> +#include <stdio_ext.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/param.h> + +#include <dirent.h> +#include <gconv_int.h> + +/* This is the default path where we look for module lists. */ +static const char default_gconv_path[] = GCONV_PATH; + +/* The path elements, as determined by the __gconv_get_path function. + All path elements end in a slash. */ +struct path_elem *__gconv_path_elem; +/* Maximum length of a single path element in __gconv_path_elem. */ +size_t __gconv_max_path_elem_len; + +/* We use the following struct if we couldn't allocate memory. */ +static const struct path_elem empty_path_elem; + +/* Name of the file containing the module information in the directories + along the path. */ +static const char gconv_conf_filename[] = "gconv-modules"; + +/* Filename extension for the modules. */ +#ifndef MODULE_EXT +# define MODULE_EXT ".so" +#endif +static const char gconv_module_ext[] = MODULE_EXT; + +/* We have a few builtin transformations. */ +static struct gconv_module builtin_modules[] = +{ +#define BUILTIN_TRANSFORMATION(From, To, Cost, Name, Fct, MinF, MaxF, \ + MinT, MaxT) \ + { \ + from_string: From, \ + to_string: To, \ + cost_hi: Cost, \ + cost_lo: INT_MAX, \ + module_name: Name \ + }, +#define BUILTIN_ALIAS(From, To) + +#include "gconv_builtin.h" +}; + +#undef BUILTIN_TRANSFORMATION +#undef BUILTIN_ALIAS + +static const char *builtin_aliases[] = +{ +#define BUILTIN_TRANSFORMATION(From, To, Cost, Name, Fct, MinF, MaxF, \ + MinT, MaxT) +#define BUILTIN_ALIAS(From, To) From " " To, + +#include "gconv_builtin.h" +}; + +#ifdef USE_IN_LIBIO +# include <libio/libioP.h> +# define __getdelim(line, len, c, fp) _IO_getdelim (line, len, c, fp) +#endif + + +/* Value of the GCONV_PATH environment variable. */ +const char *__gconv_path_envvar; + + +/* Test whether there is already a matching module known. */ +static int +internal_function +detect_conflict (const char *alias) +{ + struct gconv_module *node = __gconv_modules_db; + + while (node != NULL) + { + int cmpres = strcmp (alias, node->from_string); + + if (cmpres == 0) + /* We have a conflict. */ + return 1; + else if (cmpres < 0) + node = node->left; + else + node = node->right; + } + + return node != NULL; +} + + +/* Add new alias. */ +static inline void +add_alias (char *rp, void *modules) +{ + /* We now expect two more string. The strings are normalized + (converted to UPPER case) and strored in the alias database. */ + struct gconv_alias *new_alias; + char *from, *to, *wp; + char old_locale[20], *old_locale_p; + + /* Set locale to default C locale. */ + old_locale_p = setlocale(LC_ALL, "C"); + strncpy(old_locale, old_locale_p, 20); + + while (isspace (*rp)) + ++rp; + from = wp = rp; + while (*rp != '\0' && !isspace (*rp)) + *wp++ = toupper (*rp++); + if (*rp == '\0') + { + setlocale(LC_ALL, old_locale); + /* There is no `to' string on the line. Ignore it. */ + return; + } + *wp++ = '\0'; + to = ++rp; + while (isspace (*rp)) + ++rp; + while (*rp != '\0' && !isspace (*rp)) + *wp++ = toupper (*rp++); + if (to == wp) + { + setlocale(LC_ALL, old_locale); + /* No `to' string, ignore the line. */ + return; + } + *wp++ = '\0'; + + /* Test whether this alias conflicts with any available module. */ + if (detect_conflict (from)) + { + setlocale(LC_ALL, old_locale); + /* It does conflict, don't add the alias. */ + return; + } + + new_alias = (struct gconv_alias *) malloc (sizeof (struct gconv_alias) + (wp - from)); + if (new_alias != NULL) + { + void **inserted; + + new_alias->fromname = memcpy ((char *) new_alias + + sizeof (struct gconv_alias), + from, wp - from); + new_alias->toname = new_alias->fromname + (to - from); + + inserted = (void **) tsearch (new_alias, &__gconv_alias_db, + __gconv_alias_compare); + if (inserted == NULL || *inserted != new_alias) + /* Something went wrong, free this entry. */ + free (new_alias); + } + setlocale(LC_ALL, old_locale); +} + + +/* Insert a data structure for a new module in the search tree. */ +static inline void +internal_function +insert_module (struct gconv_module *newp, int tobefreed) +{ + struct gconv_module **rootp = &__gconv_modules_db; + + while (*rootp != NULL) + { + struct gconv_module *root = *rootp; + int cmpres; + + cmpres = strcmp (newp->from_string, root->from_string); + if (cmpres == 0) + { + /* Both strings are identical. Insert the string at the + end of the `same' list if it is not already there. */ + while (strcmp (newp->from_string, root->from_string) != 0 + || strcmp (newp->to_string, root->to_string) != 0) + { + rootp = &root->same; + root = *rootp; + if (root == NULL) + break; + } + + if (root != NULL) + { + /* This is a no new conversion. But maybe the cost is + better. */ + if (newp->cost_hi < root->cost_hi + || (newp->cost_hi == root->cost_hi + && newp->cost_lo < root->cost_lo)) + { + newp->left = root->left; + newp->right = root->right; + newp->same = root->same; + *rootp = newp; + + free (root); + } + else if (tobefreed) + free (newp); + return; + } + + break; + } + else if (cmpres < 0) + rootp = &root->left; + else + rootp = &root->right; + } + + /* Plug in the new node here. */ + *rootp = newp; +} + + +/* Add new module. */ +static void +internal_function +add_module (char *rp, const char *directory, size_t dir_len, void **modules, + size_t *nmodules, int modcounter) +{ + /* We expect now + 1. `from' name + 2. `to' name + 3. filename of the module + 4. an optional cost value + */ + struct gconv_alias fake_alias; + struct gconv_module *new_module; + char *from, *to, *module, *wp; + int need_ext; + int cost_hi; + char old_locale[20], *old_locale_p; + char *old; + size_t len; + char *new; + + /* Set locale to default C locale. */ + old_locale_p = setlocale(LC_ALL, "C"); + strncpy(old_locale, old_locale_p, 20); + + while (isspace (*rp)) + ++rp; + from = rp; + while (*rp != '\0' && !isspace (*rp)) + { + *rp = toupper (*rp); + ++rp; + } + if (*rp == '\0') + { + setlocale(LC_ALL, old_locale); + return; + } + *rp++ = '\0'; + to = wp = rp; + while (isspace (*rp)) + { + setlocale(LC_ALL, old_locale); + ++rp; + } + while (*rp != '\0' && !isspace (*rp)) + *wp++ = toupper (*rp++); + if (*rp == '\0') + { + setlocale(LC_ALL, old_locale); + return; + } + *wp++ = '\0'; + do + ++rp; + while (isspace (*rp)); + module = wp; + while (*rp != '\0' && !isspace (*rp)) + *wp++ = *rp++; + if (*rp == '\0') + { + /* There is no cost, use one by default. */ + *wp++ = '\0'; + cost_hi = 1; + } + else + { + /* There might be a cost value. */ + char *endp; + + *wp++ = '\0'; + cost_hi = strtol (rp, &endp, 10); + if (rp == endp || cost_hi < 1) + /* No useful information. */ + cost_hi = 1; + } + + if (module[0] == '\0') + { + setlocale(LC_ALL, old_locale); + /* No module name given. */ + return; + } + if (module[0] == '/') + dir_len = 0; + + /* See whether we must add the ending. */ + need_ext = 0; + if (wp - module < (ptrdiff_t) sizeof (gconv_module_ext) + || memcmp (wp - sizeof (gconv_module_ext), gconv_module_ext, + sizeof (gconv_module_ext)) != 0) + /* We must add the module extension. */ + need_ext = sizeof (gconv_module_ext) - 1; + + /* See whether we have already an alias with this name defined. */ + old = from; + len = strnlen (old, to - from); + new = (char *) alloca (len + 1); + new[len] = '\0'; + fake_alias.fromname = (char *) memcpy (new, old, len); + + if (tfind (&fake_alias, &__gconv_alias_db, __gconv_alias_compare) != NULL) + { + setlocale(LC_ALL, old_locale); + /* This module duplicates an alias. */ + return; + } + + new_module = (struct gconv_module *) calloc (1, + sizeof (struct gconv_module) + + (wp - from) + + dir_len + need_ext); + if (new_module != NULL) + { + char *tmp; + + new_module->from_string = tmp = (char *) (new_module + 1); + tmp = memcpy (tmp, from, to - from); + tmp += (to - from); + + new_module->to_string = tmp; + tmp = memcpy (tmp, to, module - to); + tmp += (module - to); + + new_module->cost_hi = cost_hi; + new_module->cost_lo = modcounter; + + new_module->module_name = tmp; + + if (dir_len != 0) + { + tmp = memcpy (tmp, directory, dir_len); + tmp += dir_len; + } + + tmp = memcpy (tmp, module, wp - module); + tmp += (wp - module); + + if (need_ext) + memcpy (tmp - 1, gconv_module_ext, sizeof (gconv_module_ext)); + + /* Now insert the new module data structure in our search tree. */ + insert_module (new_module, 1); + } + setlocale(LC_ALL, old_locale); +} + + +/* Read the next configuration file. */ +static void +internal_function +read_conf_file (const char *filename, const char *directory, size_t dir_len, + void **modules, size_t *nmodules) +{ + FILE *fp = fopen (filename, "r"); + char *line = NULL; + size_t line_len = 0; + static int modcounter; + char old_locale[20], *old_locale_p; + + /* Don't complain if a file is not present or readable, simply silently + ignore it. */ + if (fp == NULL) + return; + + /* Set locale to default C locale. */ + old_locale_p = setlocale(LC_ALL, "C"); + strncpy(old_locale, old_locale_p, 20); + + /* Process the known entries of the file. Comments start with `#' and + end with the end of the line. Empty lines are ignored. */ + while (!feof (fp)) + { + char *rp, *endp, *word; + ssize_t n = __getdelim (&line, &line_len, '\n', fp); + if (n < 0) + /* An error occurred. */ + break; + + rp = line; + /* Terminate the line (excluding comments or newline) by an NUL byte + to simplify the following code. */ + endp = strchr (rp, '#'); + if (endp != NULL) + *endp = '\0'; + else + if (rp[n - 1] == '\n') + rp[n - 1] = '\0'; + + while (isspace (*rp)) + ++rp; + + /* If this is an empty line go on with the next one. */ + if (rp == endp) + continue; + + word = rp; + while (*rp != '\0' && !isspace (*rp)) + ++rp; + + if (rp - word == sizeof ("alias") - 1 + && memcmp (word, "alias", sizeof ("alias") - 1) == 0) + add_alias (rp, *modules); + else if (rp - word == sizeof ("module") - 1 + && memcmp (word, "module", sizeof ("module") - 1) == 0) + add_module (rp, directory, dir_len, modules, nmodules, modcounter++); + /* else */ + /* Otherwise ignore the line. */ + } + + free (line); + + fclose (fp); + + setlocale(LC_ALL, old_locale); +} + + +/* Determine the directories we are looking for data in. */ +void +__gconv_get_path (void) +{ + struct path_elem *result; + __LOCK_INIT(static, path_lock); + +#ifdef HAVE_DD_LOCK + __lock_acquire(path_lock); +#endif + + /* Make sure there wasn't a second thread doing it already. */ + result = (struct path_elem *) __gconv_path_elem; + if (result == NULL) + { + /* Determine the complete path first. */ + char *gconv_path; + size_t gconv_path_len; + char *elem; + char *oldp; + char *cp; + int nelems; + char *cwd; + size_t cwdlen; + + if (__gconv_path_envvar == NULL) + { + char * old = default_gconv_path; + size_t len = strlen (old) + 1; + char *new = (char *) alloca (len); + + /* No user-defined path. Make a modifiable copy of the + default path. */ + gconv_path = (char *) memcpy (new, old, len); + gconv_path_len = sizeof (default_gconv_path); + cwd = NULL; + cwdlen = 0; + } + else + { + /* Append the default path to the user-defined path. */ + size_t user_len = strlen (__gconv_path_envvar); + char *tmp; + + gconv_path_len = user_len + 1 + sizeof (default_gconv_path); + gconv_path = alloca (gconv_path_len); + tmp = memcpy (gconv_path, __gconv_path_envvar, + user_len); + tmp += user_len; + memcpy (tmp, ":", 1); + tmp += 1; + memcpy (tmp, + default_gconv_path, sizeof (default_gconv_path)); + + cwd = getcwd (NULL, 0); + cwdlen = strlen (cwd); + } + assert (default_gconv_path[0] == '/'); + + /* In a first pass we calculate the number of elements. */ + oldp = NULL; + cp = strchr (gconv_path, ':'); + nelems = 1; + while (cp != NULL) + { + if (cp != oldp + 1) + ++nelems; + oldp = cp; + cp = strchr (cp + 1, ':'); + } + + /* Allocate the memory for the result. */ + result = (struct path_elem *) malloc ((nelems + 1) + * sizeof (struct path_elem) + + gconv_path_len + nelems + + (nelems - 1) * (cwdlen + 1)); + if (result != NULL) + { + char *strspace = (char *) &result[nelems + 1]; + int n = 0; + + /* Separate the individual parts. */ + __gconv_max_path_elem_len = 0; + elem = strtok_r (gconv_path, ":", &gconv_path); + assert (elem != NULL); + do + { + result[n].name = strspace; + if (elem[0] != '/') + { + assert (cwd != NULL); + strspace = memcpy (strspace, cwd, cwdlen); + strspace += cwdlen; + *strspace++ = '/'; + } + strspace = strcpy (strspace, elem); + while(*strspace != '\0') strspace++; + + if (strspace[-1] != '/') + *strspace++ = '/'; + + result[n].len = strspace - result[n].name; + if (result[n].len > __gconv_max_path_elem_len) + __gconv_max_path_elem_len = result[n].len; + + *strspace++ = '\0'; + ++n; + } + while ((elem = strtok_r (NULL, ":", &gconv_path)) != NULL); + + result[n].name = NULL; + result[n].len = 0; + } + + __gconv_path_elem = result ?: (struct path_elem *) &empty_path_elem; + + if (cwd != NULL) + free (cwd); + } + +#ifdef HAVE_DD_LOCK + __lock_release(path_lock); +#endif +} + + +/* Read all configuration files found in the user-specified and the default + path. */ +void +__gconv_read_conf (void) +{ + void *modules = NULL; + size_t nmodules = 0; + int save_errno = errno; + size_t cnt; + char *filename; + char *tmp; + const char *elem; + size_t elem_len; + + /* First see whether we should use the cache. */ + if (__gconv_load_cache () == 0) + { + /* Yes, we are done. */ + __set_errno (save_errno); + return; + } + +#ifndef STATIC_GCONV + /* Find out where we have to look. */ + if (__gconv_path_elem == NULL) + __gconv_get_path (); + + for (cnt = 0; __gconv_path_elem[cnt].name != NULL; ++cnt) + { + elem = __gconv_path_elem[cnt].name; + elem_len = __gconv_path_elem[cnt].len; + + /* No slash needs to be inserted between elem and gconv_conf_filename; + elem already ends in a slash. */ + filename = alloca (elem_len + sizeof (gconv_conf_filename)); + tmp = memcpy (filename, elem, elem_len); + tmp += elem_len; + memcpy (tmp, gconv_conf_filename, sizeof (gconv_conf_filename)); + + /* Read the next configuration file. */ + read_conf_file (filename, elem, elem_len, &modules, &nmodules); + } +#endif + + /* Add the internal modules. */ + for (cnt = 0; cnt < sizeof (builtin_modules) / sizeof (builtin_modules[0]); + ++cnt) + { + struct gconv_alias fake_alias; + + fake_alias.fromname = (char *) builtin_modules[cnt].from_string; + + if (tfind (&fake_alias, &__gconv_alias_db, __gconv_alias_compare) + != NULL) + /* It'll conflict so don't add it. */ + continue; + + insert_module (&builtin_modules[cnt], 0); + } + + /* Add aliases for builtin conversions. */ + cnt = sizeof (builtin_aliases) / sizeof (builtin_aliases[0]); + while (cnt > 0) + { + char * old = builtin_aliases[--cnt]; + size_t len = strlen (old) + 1; + char *new = (char *) alloca (len); + char *copy = (char *) memcpy (new, old, len); + + add_alias (copy, modules); + } + + /* Restore the error number. */ + __set_errno (save_errno); +} + + + +/* Free all resources if necessary. */ +static void __attribute__ ((unused)) +free_mem (void) +{ + if (__gconv_path_elem != NULL && __gconv_path_elem != &empty_path_elem) + free ((void *) __gconv_path_elem); +} + +text_set_element (__libc_subfreeres, free_mem); |