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