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

source code of glibc/iconv/gconv_db.c