source: trunk/libs/newlib/src/newlib/libc/sys/linux/iconv/gconv_db.c @ 444

Last change on this file since 444 was 444, checked in by satin@…, 6 years ago

add newlib,libalmos-mkh, restructure shared_syscalls.h and mini-libc

File size: 21.9 KB
Line 
1/* Provide access to the collection of available transformation modules.
2   Copyright (C) 1997,98,99,2000,2001 Free Software Foundation, Inc.
3   This file is part of the GNU C Library.
4   Contributed by Ulrich Drepper <drepper@cygnus.com>, 1997.
5
6   The GNU C Library is free software; you can redistribute it and/or
7   modify it under the terms of the GNU Lesser General Public
8   License as published by the Free Software Foundation; either
9   version 2.1 of the License, or (at your option) any later version.
10
11   The GNU C Library is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   Lesser General Public License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with the GNU C Library; if not, write to the Free
18   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19   02111-1307 USA.  */
20
21#include <limits.h>
22#include <search.h>
23#include <stdlib.h>
24#include <string.h>
25#include <sys/param.h>
26#include <dirent.h>
27
28#include <dlfcn.h>
29#include <gconv_int.h>
30#include <gconv_charset.h>
31
32
33/* Simple data structure for alias mapping.  We have two names, `from'
34   and `to'.  */
35void *__gconv_alias_db;
36
37/* Array with available modules.  */
38struct gconv_module *__gconv_modules_db;
39
40/* We modify global data.   */
41__LOCK_INIT(static, lock);
42
43
44/* Function for searching alias.  */
45int
46__gconv_alias_compare (const void *p1, const void *p2)
47{
48  const struct gconv_alias *s1 = (const struct gconv_alias *) p1;
49  const struct gconv_alias *s2 = (const struct gconv_alias *) p2;
50  return strcmp (s1->fromname, s2->fromname);
51}
52
53
54/* To search for a derivation we create a list of intermediate steps.
55   Each element contains a pointer to the element which precedes it
56   in the derivation order.  */
57struct derivation_step
58{
59  const char *result_set;
60  size_t result_set_len;
61  int cost_lo;
62  int cost_hi;
63  struct gconv_module *code;
64  struct derivation_step *last;
65  struct derivation_step *next;
66};
67
68#define NEW_STEP(result, hi, lo, module, last_mod) \
69  ({ struct derivation_step *newp = alloca (sizeof (struct derivation_step)); \
70     newp->result_set = result;                                               \
71     newp->result_set_len = strlen (result);                                  \
72     newp->cost_hi = hi;                                                      \
73     newp->cost_lo = lo;                                                      \
74     newp->code = module;                                                     \
75     newp->last = last_mod;                                                   \
76     newp->next = NULL;                                                       \
77     newp; })
78
79
80/* If a specific transformation is used more than once we should not need
81   to start looking for it again.  Instead cache each successful result.  */
82struct known_derivation
83{
84  const char *from;
85  const char *to;
86  struct __gconv_step *steps;
87  size_t nsteps;
88};
89
90/* Compare function for database of found derivations.  */
91static int
92derivation_compare (const void *p1, const void *p2)
93{
94  const struct known_derivation *s1 = (const struct known_derivation *) p1;
95  const struct known_derivation *s2 = (const struct known_derivation *) p2;
96  int result;
97
98  result = strcmp (s1->from, s2->from);
99  if (result == 0)
100    result = strcmp (s1->to, s2->to);
101  return result;
102}
103
104/* The search tree for known derivations.  */
105static void *known_derivations;
106
107/* Look up whether given transformation was already requested before.  */
108static int
109internal_function
110derivation_lookup (const char *fromset, const char *toset,
111                   struct __gconv_step **handle, size_t *nsteps)
112{
113  struct known_derivation key = { fromset, toset, NULL, 0 };
114  struct known_derivation **result;
115
116  result = tfind (&key, &known_derivations, derivation_compare);
117
118  if (result == NULL)
119    return __GCONV_NOCONV;
120
121  *handle = (*result)->steps;
122  *nsteps = (*result)->nsteps;
123
124  /* Please note that we return GCONV_OK even if the last search for
125     this transformation was unsuccessful.  */
126  return __GCONV_OK;
127}
128
129/* Add new derivation to list of known ones.  */
130static void
131internal_function
132add_derivation (const char *fromset, const char *toset,
133                struct __gconv_step *handle, size_t nsteps)
134{
135  struct known_derivation *new_deriv;
136  size_t fromset_len = strlen (fromset) + 1;
137  size_t toset_len = strlen (toset) + 1;
138
139  new_deriv = (struct known_derivation *)
140    malloc (sizeof (struct known_derivation) + fromset_len + toset_len);
141  if (new_deriv != NULL)
142    {
143      char *tmp;
144      new_deriv->from = (char *) (new_deriv + 1);
145      tmp = memcpy (new_deriv + 1, fromset, fromset_len);
146      tmp += fromset_len;
147
148      new_deriv->to = memcpy (tmp,
149                              toset, toset_len);
150
151      new_deriv->steps = handle;
152      new_deriv->nsteps = nsteps;
153
154      if (tsearch (new_deriv, &known_derivations, derivation_compare)
155          == NULL)
156        /* There is some kind of memory allocation problem.  */
157        free (new_deriv);
158    }
159  /* Please note that we don't complain if the allocation failed.  This
160     is not tragically but in case we use the memory debugging facilities
161     not all memory will be freed.  */
162}
163
164static void
165free_derivation (void *p)
166{
167  struct known_derivation *deriv = (struct known_derivation *) p;
168  size_t cnt;
169
170  for (cnt = 0; cnt < deriv->nsteps; ++cnt)
171    if (deriv->steps[cnt].__counter > 0
172        && deriv->steps[cnt].__end_fct != NULL)
173      deriv->steps[cnt].__end_fct (&deriv->steps[cnt]);
174
175  /* Free the name strings.  */
176  free ((char *) deriv->steps[0].__from_name);
177  free ((char *) deriv->steps[deriv->nsteps - 1].__to_name);
178
179  free ((struct __gconv_step *) deriv->steps);
180  free (deriv);
181}
182
183
184/* Decrement the reference count for a single step in a steps array.  */
185void
186internal_function
187__gconv_release_step (struct __gconv_step *step)
188{
189  if (--step->__counter == 0)
190    {
191      /* Call the destructor.  */
192      if (step->__end_fct != NULL)
193        step->__end_fct (step);
194
195#ifndef STATIC_GCONV
196      /* Skip builtin modules; they are not reference counted.  */
197      if (step->__shlib_handle != NULL)
198        {
199          /* Release the loaded module.  */
200          __gconv_release_shlib (step->__shlib_handle);
201          step->__shlib_handle = NULL;
202        }
203#endif
204    }
205}
206
207static int
208internal_function
209gen_steps (struct derivation_step *best, const char *toset,
210           const char *fromset, struct __gconv_step **handle, size_t *nsteps)
211{
212  size_t step_cnt = 0;
213  struct __gconv_step *result;
214  struct derivation_step *current;
215  int status = __GCONV_NOMEM;
216
217  /* First determine number of steps.  */
218  for (current = best; current->last != NULL; current = current->last)
219    ++step_cnt;
220
221  result = (struct __gconv_step *) malloc (sizeof (struct __gconv_step)
222                                           * step_cnt);
223  if (result != NULL)
224    {
225      int failed = 0;
226
227      status = __GCONV_OK;
228      *nsteps = step_cnt;
229      current = best;
230      while (step_cnt-- > 0)
231        {
232          result[step_cnt].__from_name = (step_cnt == 0
233                                          ? strdup (fromset)
234                                          : (char *)current->last->result_set);
235          result[step_cnt].__to_name = (step_cnt + 1 == *nsteps
236                                        ? strdup (current->result_set)
237                                        : result[step_cnt + 1].__from_name);
238
239          result[step_cnt].__counter = 1;
240          result[step_cnt].__data = NULL;
241
242#ifndef STATIC_GCONV
243          if (current->code->module_name[0] == '/')
244            {
245              /* Load the module, return handle for it.  */
246              struct __gconv_loaded_object *shlib_handle =
247                __gconv_find_shlib (current->code->module_name);
248
249              if (shlib_handle == NULL)
250                {
251                  failed = 1;
252                  break;
253                }
254
255              result[step_cnt].__shlib_handle = shlib_handle;
256              result[step_cnt].__modname = shlib_handle->name;
257              result[step_cnt].__fct = shlib_handle->fct;
258              result[step_cnt].__init_fct = shlib_handle->init_fct;
259              result[step_cnt].__end_fct = shlib_handle->end_fct;
260
261              /* Call the init function.  */
262              if (result[step_cnt].__init_fct != NULL)
263                {
264                  status = result[step_cnt].__init_fct (&result[step_cnt]);
265
266                  if (__builtin_expect (status, __GCONV_OK) != __GCONV_OK)
267                    {
268                      failed = 1;
269                      /* Make sure we unload this modules.  */
270                      --step_cnt;
271                      result[step_cnt].__end_fct = NULL;
272                      break;
273                    }
274                }
275            }
276          else
277#endif
278            /* It's a builtin transformation.  */
279            __gconv_get_builtin_trans (current->code->module_name,
280                                       &result[step_cnt]);
281
282          current = current->last;
283        }
284
285      if (__builtin_expect (failed, 0) != 0)
286        {
287          /* Something went wrong while initializing the modules.  */
288          while (++step_cnt < *nsteps)
289            __gconv_release_step (&result[step_cnt]);
290          free (result);
291          *nsteps = 0;
292          *handle = NULL;
293          if (status == __GCONV_OK)
294            status = __GCONV_NOCONV;
295        }
296      else
297        *handle = result;
298    }
299  else
300    {
301      *nsteps = 0;
302      *handle = NULL;
303    }
304
305  return status;
306}
307
308
309#ifndef STATIC_GCONV
310static int
311internal_function
312increment_counter (struct __gconv_step *steps, size_t nsteps)
313{
314  /* Increment the user counter.  */
315  size_t cnt = nsteps;
316  int result = __GCONV_OK;
317
318  while (cnt-- > 0)
319    {
320      struct __gconv_step *step = &steps[cnt];
321
322      if (step->__counter++ == 0)
323        {
324          /* Skip builtin modules.  */
325          if (step->__modname != NULL)
326            {
327              /* Reopen a previously used module.  */
328              step->__shlib_handle = __gconv_find_shlib (step->__modname);
329              if (step->__shlib_handle == NULL)
330                {
331                  /* Oops, this is the second time we use this module
332                     (after unloading) and this time loading failed!?  */
333                  --step->__counter;
334                  while (++cnt < nsteps)
335                    __gconv_release_step (&steps[cnt]);
336                  result = __GCONV_NOCONV;
337                  break;
338                }
339
340              /* The function addresses defined by the module may
341                 have changed.  */
342              step->__fct = step->__shlib_handle->fct;
343              step->__init_fct = step->__shlib_handle->init_fct;
344              step->__end_fct = step->__shlib_handle->end_fct;
345            }
346
347          if (step->__init_fct != NULL)
348            step->__init_fct (step);
349        }
350    }
351  return result;
352}
353#endif
354
355
356/* The main function: find a possible derivation from the `fromset' (either
357   the given name or the alias) to the `toset' (again with alias).  */
358static int
359internal_function
360find_derivation (const char *toset, const char *toset_expand,
361                 const char *fromset, const char *fromset_expand,
362                 struct __gconv_step **handle, size_t *nsteps)
363{
364  struct derivation_step *first, *current, **lastp, *solution = NULL;
365  int best_cost_hi = INT_MAX;
366  int best_cost_lo = INT_MAX;
367  int result;
368
369  /* Look whether an earlier call to `find_derivation' has already
370     computed a possible derivation.  If so, return it immediately.  */
371  result = derivation_lookup (fromset_expand ?: fromset, toset_expand ?: toset,
372                              handle, nsteps);
373  if (result == __GCONV_OK)
374    {
375#ifndef STATIC_GCONV
376      result = increment_counter (*handle, *nsteps);
377#endif
378      return result;
379    }
380
381  /* The task is to find a sequence of transformations, backed by the
382     existing modules - whether builtin or dynamically loadable -,
383     starting at `fromset' (or `fromset_expand') and ending at `toset'
384     (or `toset_expand'), and with minimal cost.
385
386     For computer scientists, this is a shortest path search in the
387     graph where the nodes are all possible charsets and the edges are
388     the transformations listed in __gconv_modules_db.
389
390     For now we use a simple algorithm with quadratic runtime behaviour.
391     A breadth-first search, starting at `fromset' and `fromset_expand'.
392     The list starting at `first' contains all nodes that have been
393     visited up to now, in the order in which they have been visited --
394     excluding the goal nodes `toset' and `toset_expand' which get
395     managed in the list starting at `solution'.
396     `current' walks through the list starting at `first' and looks
397     which nodes are reachable from the current node, adding them to
398     the end of the list [`first' or `solution' respectively] (if
399     they are visited the first time) or updating them in place (if
400     they have have already been visited).
401     In each node of either list, cost_lo and cost_hi contain the
402     minimum cost over any paths found up to now, starting at `fromset'
403     or `fromset_expand', ending at that node.  best_cost_lo and
404     best_cost_hi represent the minimum over the elements of the
405     `solution' list.  */
406
407  if (fromset_expand != NULL)
408    {
409      first = NEW_STEP (fromset_expand, 0, 0, NULL, NULL);
410      first->next = NEW_STEP (fromset, 0, 0, NULL, NULL);
411      lastp = &first->next->next;
412    }
413  else
414    {
415      first = NEW_STEP (fromset, 0, 0, NULL, NULL);
416      lastp = &first->next;
417    }
418
419  for (current = first; current != NULL; current = current->next)
420    {
421      /* Now match all the available module specifications against the
422         current charset name.  If any of them matches check whether
423         we already have a derivation for this charset.  If yes, use the
424         one with the lower costs.  Otherwise add the new charset at the
425         end.
426
427         The module database is organized in a tree form which allows
428         searching for prefixes.  So we search for the first entry with a
429         matching prefix and any other matching entry can be found from
430         this place.  */
431      struct gconv_module *node;
432
433      /* Maybe it is not necessary anymore to look for a solution for
434         this entry since the cost is already as high (or higher) as
435         the cost for the best solution so far.  */
436      if (current->cost_hi > best_cost_hi
437          || (current->cost_hi == best_cost_hi
438              && current->cost_lo >= best_cost_lo))
439        continue;
440
441      node = __gconv_modules_db;
442      while (node != NULL)
443        {
444          int cmpres = strcmp (current->result_set, node->from_string);
445          if (cmpres == 0)
446            {
447              /* Walk through the list of modules with this prefix and
448                 try to match the name.  */
449              struct gconv_module *runp;
450
451              /* Check all the modules with this prefix.  */
452              runp = node;
453              do
454                {
455                  const char *result_set = (strcmp (runp->to_string, "-") == 0
456                                            ? (toset_expand ?: toset)
457                                            : runp->to_string);
458                  int cost_hi = runp->cost_hi + current->cost_hi;
459                  int cost_lo = runp->cost_lo + current->cost_lo;
460                  struct derivation_step *step;
461
462                  /* We managed to find a derivation.  First see whether
463                     we have reached one of the goal nodes.  */
464                  if (strcmp (result_set, toset) == 0
465                      || (toset_expand != NULL
466                          && strcmp (result_set, toset_expand) == 0))
467                    {
468                      /* Append to the `solution' list if there
469                         is no entry with this name.  */
470                      for (step = solution; step != NULL; step = step->next)
471                        if (strcmp (result_set, step->result_set) == 0)
472                          break;
473
474                      if (step == NULL)
475                        {
476                          step = NEW_STEP (result_set,
477                                           cost_hi, cost_lo,
478                                           runp, current);
479                          step->next = solution;
480                          solution = step;
481                        }
482                      else if (step->cost_hi > cost_hi
483                               || (step->cost_hi == cost_hi
484                                   && step->cost_lo > cost_lo))
485                        {
486                          /* A better path was found for the node,
487                             on the `solution' list.  */
488                          step->code = runp;
489                          step->last = current;
490                          step->cost_hi = cost_hi;
491                          step->cost_lo = cost_lo;
492                        }
493
494                      /* Update best_cost accordingly.  */
495                      if (cost_hi < best_cost_hi
496                          || (cost_hi == best_cost_hi
497                              && cost_lo < best_cost_lo))
498                        {
499                          best_cost_hi = cost_hi;
500                          best_cost_lo = cost_lo;
501                        }
502                    }
503                  else if (cost_hi < best_cost_hi
504                           || (cost_hi == best_cost_hi
505                               && cost_lo < best_cost_lo))
506                    {
507                      /* Append at the end of the `first' list if there
508                         is no entry with this name.  */
509                      for (step = first; step != NULL; step = step->next)
510                        if (strcmp (result_set, step->result_set) == 0)
511                          break;
512
513                      if (step == NULL)
514                        {
515                          *lastp = NEW_STEP (result_set,
516                                             cost_hi, cost_lo,
517                                             runp, current);
518                          lastp = &(*lastp)->next;
519                        }
520                      else if (step->cost_hi > cost_hi
521                               || (step->cost_hi == cost_hi
522                                   && step->cost_lo > cost_lo))
523                        {
524                          /* A better path was found for the node,
525                             on the `first' list.  */
526                          step->code = runp;
527                          step->last = current;
528
529                          /* Update the cost for all steps.  */
530                          for (step = first; step != NULL;
531                               step = step->next)
532                            /* But don't update the start nodes.  */
533                            if (step->code != NULL)
534                              {
535                                struct derivation_step *back;
536                                int hi, lo;
537
538                                hi = step->code->cost_hi;
539                                lo = step->code->cost_lo;
540
541                                for (back = step->last; back->code != NULL;
542                                     back = back->last)
543                                  {
544                                    hi += back->code->cost_hi;
545                                    lo += back->code->cost_lo;
546                                  }
547
548                                step->cost_hi = hi;
549                                step->cost_lo = lo;
550                              }
551
552                          /* Likewise for the nodes on the solution list.
553                             Also update best_cost accordingly.  */
554                          for (step = solution; step != NULL;
555                               step = step->next)
556                            {
557                              step->cost_hi = (step->code->cost_hi
558                                               + step->last->cost_hi);
559                              step->cost_lo = (step->code->cost_lo
560                                               + step->last->cost_lo);
561
562                              if (step->cost_hi < best_cost_hi
563                                  || (step->cost_hi == best_cost_hi
564                                      && step->cost_lo < best_cost_lo))
565                                {
566                                  best_cost_hi = step->cost_hi;
567                                  best_cost_lo = step->cost_lo;
568                                }
569                            }
570                        }
571                    }
572
573                  runp = runp->same;
574                }
575              while (runp != NULL);
576
577              break;
578            }
579          else if (cmpres < 0)
580            node = node->left;
581          else
582            node = node->right;
583        }
584    }
585
586  if (solution != NULL)
587    {
588      /* We really found a way to do the transformation.  */
589
590      /* Choose the best solution.  This is easy because we know that
591         the solution list has at most length 2 (one for every possible
592         goal node).  */
593      if (solution->next != NULL)
594        {
595          struct derivation_step *solution2 = solution->next;
596
597          if (solution2->cost_hi < solution->cost_hi
598              || (solution2->cost_hi == solution->cost_hi
599                  && solution2->cost_lo < solution->cost_lo))
600            solution = solution2;
601        }
602
603      /* Now build a data structure describing the transformation steps.  */
604      result = gen_steps (solution, toset_expand ?: toset,
605                          fromset_expand ?: fromset, handle, nsteps);
606    }
607  else
608    {
609      /* We haven't found a transformation.  Clear the result values.  */
610      *handle = NULL;
611      *nsteps = 0;
612    }
613
614  /* Add result in any case to list of known derivations.  */
615  add_derivation (fromset_expand ?: fromset, toset_expand ?: toset,
616                  *handle, *nsteps);
617
618  return result;
619}
620
621
622/* Control of initialization.  */
623__libc_once_define (static, once);
624
625
626static const char *
627do_lookup_alias (const char *name)
628{
629  struct gconv_alias key;
630  struct gconv_alias **found;
631
632  key.fromname = (char *) name;
633  found = tfind (&key, &__gconv_alias_db, __gconv_alias_compare);
634  return found != NULL ? (*found)->toname : NULL;
635}
636
637
638int
639internal_function
640__gconv_compare_alias (const char *name1, const char *name2)
641{
642  int result;
643
644  /* Ensure that the configuration data is read.  */
645  __libc_once (once, __gconv_read_conf);
646
647  if (__gconv_compare_alias_cache (name1, name2, &result) != 0)
648    result = strcmp (do_lookup_alias (name1) ?: name1,
649                     do_lookup_alias (name2) ?: name2);
650
651  return result;
652}
653
654
655int
656internal_function
657__gconv_find_transform (const char *toset, const char *fromset,
658                        struct __gconv_step **handle, size_t *nsteps,
659                        int flags)
660{
661  const char *fromset_expand;
662  const char *toset_expand;
663  int result;
664
665  /* Ensure that the configuration data is read.  */
666  __libc_once (once, __gconv_read_conf);
667
668  /* Acquire the lock.  */
669#ifdef HAVE_DD_LOCK
670  __lock_acquire(lock);
671#endif
672
673  result = __gconv_lookup_cache (toset, fromset, handle, nsteps, flags);
674  if (result != __GCONV_NODB)
675    {
676      /* We have a cache and could resolve the request, successful or not.  */
677#ifdef HAVE_DD_LOCK
678      __lock_release(lock);
679#endif
680
681      return result;
682    }
683
684  /* If we don't have a module database return with an error.  */
685  if (__gconv_modules_db == NULL)
686    {
687#ifdef HAVE_DD_LOCK
688  __lock_release(lock);
689#endif
690
691      return __GCONV_NOCONV;
692    }
693
694  /* See whether the names are aliases.  */
695  fromset_expand = do_lookup_alias (fromset);
696  toset_expand = do_lookup_alias (toset);
697
698  if (__builtin_expect (flags & GCONV_AVOID_NOCONV, 0)
699      /* We are not supposed to create a pseudo transformation (means
700         copying) when the input and output character set are the same.  */
701      && (strcmp (toset, fromset) == 0
702          || (toset_expand != NULL && strcmp (toset_expand, fromset) == 0)
703          || (fromset_expand != NULL
704              && (strcmp (toset, fromset_expand) == 0
705                  || (toset_expand != NULL
706                      && strcmp (toset_expand, fromset_expand) == 0)))))
707    {
708      /* Both character sets are the same.  */
709#ifdef HAVE_DD_LOCK
710  __lock_release(lock);
711#endif
712
713      return __GCONV_NOCONV;
714    }
715
716  result = find_derivation (toset, toset_expand, fromset, fromset_expand,
717                            handle, nsteps);
718
719  /* Release the lock.  */
720#ifdef HAVE_DD_LOCK
721  __lock_release(lock);
722#endif
723
724
725  /* The following code is necessary since `find_derivation' will return
726     GCONV_OK even when no derivation was found but the same request
727     was processed before.  I.e., negative results will also be cached.  */
728  return (result == __GCONV_OK
729          ? (*handle == NULL ? __GCONV_NOCONV : __GCONV_OK)
730          : result);
731}
732
733
734/* Release the entries of the modules list.  */
735int
736internal_function
737__gconv_close_transform (struct __gconv_step *steps, size_t nsteps)
738{
739  int result = __GCONV_OK;
740  size_t cnt;
741
742  /* Acquire the lock.  */
743#ifdef HAVE_DD_LOCK
744  __lock_acquire(lock);
745#endif
746
747
748#ifndef STATIC_GCONV
749  cnt = nsteps;
750  while (cnt-- > 0)
751    __gconv_release_step (&steps[cnt]);
752#endif
753
754  /* If we use the cache we free a bit more since we don't keep any
755     transformation records around, they are cheap enough to
756     recreate.  */
757  __gconv_release_cache (steps, nsteps);
758
759  /* Release the lock.  */
760#ifdef HAVE_DD_LOCK
761  __lock_release(lock);
762#endif
763
764
765  return result;
766}
767
768
769/* Free the modules mentioned.  */
770static void
771internal_function
772free_modules_db (struct gconv_module *node)
773{
774  if (node->left != NULL)
775    free_modules_db (node->left);
776  if (node->right != NULL)
777    free_modules_db (node->right);
778  do
779    {
780      struct gconv_module *act = node;
781      node = node->same;
782      if (act->module_name[0] == '/')
783        free (act);
784    }
785  while (node != NULL);
786}
787
788
789/* Free all resources if necessary.  */
790static void __attribute__ ((unused))
791free_mem (void)
792{
793  if (__gconv_alias_db != NULL)
794    tdestroy (__gconv_alias_db, free);
795
796  if (__gconv_modules_db != NULL)
797    free_modules_db (__gconv_modules_db);
798
799  if (known_derivations != NULL)
800    tdestroy (known_derivations, free_derivation);
801}
802
803text_set_element (__libc_subfreeres, free_mem);
Note: See TracBrowser for help on using the repository browser.