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'. */
35 void *__gconv_alias_db;
36
37 /* Array with available modules. */
38 struct gconv_module *__gconv_modules_db;
39
40 /* We modify global data. */
41 __LOCK_INIT(static, lock);
42
43
44 /* Function for searching alias. */
45 int
__gconv_alias_compare(const void * p1,const void * p2)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. */
57 struct 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. */
82 struct 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. */
91 static int
derivation_compare(const void * p1,const void * p2)92 derivation_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. */
105 static void *known_derivations;
106
107 /* Look up whether given transformation was already requested before. */
108 static int
109 internal_function
derivation_lookup(const char * fromset,const char * toset,struct __gconv_step ** handle,size_t * nsteps)110 derivation_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. */
130 static void
131 internal_function
add_derivation(const char * fromset,const char * toset,struct __gconv_step * handle,size_t nsteps)132 add_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
164 static void
free_derivation(void * p)165 free_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. */
185 void
186 internal_function
__gconv_release_step(struct __gconv_step * step)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
207 static int
208 internal_function
gen_steps(struct derivation_step * best,const char * toset,const char * fromset,struct __gconv_step ** handle,size_t * nsteps)209 gen_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
310 static int
311 internal_function
increment_counter(struct __gconv_step * steps,size_t nsteps)312 increment_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). */
358 static int
359 internal_function
find_derivation(const char * toset,const char * toset_expand,const char * fromset,const char * fromset_expand,struct __gconv_step ** handle,size_t * nsteps)360 find_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
626 static const char *
do_lookup_alias(const char * name)627 do_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
638 int
639 internal_function
__gconv_compare_alias(const char * name1,const char * name2)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
655 int
656 internal_function
__gconv_find_transform(const char * toset,const char * fromset,struct __gconv_step ** handle,size_t * nsteps,int flags)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. */
735 int
736 internal_function
__gconv_close_transform(struct __gconv_step * steps,size_t nsteps)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. */
770 static void
771 internal_function
free_modules_db(struct gconv_module * node)772 free_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. */
790 static void __attribute__ ((unused))
free_mem(void)791 free_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
803 text_set_element (__libc_subfreeres, free_mem);
804