packaging: Enable memcpy in sysdeps/arm/memcpy.S for ARM
[platform/upstream/glibc.git] / iconv / gconv_db.c
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'.  */
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 __libc_lock_define_initialized (, __gconv_lock)
42
43
44 /* Provide access to module database.  */
45 struct gconv_module *
46 __gconv_get_modules_db (void)
47 {
48   return __gconv_modules_db;
49 }
50
51 void *
52 __gconv_get_alias_db (void)
53 {
54   return __gconv_alias_db;
55 }
56
57
58 /* Function for searching alias.  */
59 int
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.  */
71 struct 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.  */
96 struct 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.  */
105 static int
106 derivation_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.  */
119 static void *known_derivations;
120
121 /* Look up whether given transformation was already requested before.  */
122 static int
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 add_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 (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 (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
172 static void
173 free_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 ((char *) deriv->steps[0].__from_name);
192       free ((char *) deriv->steps[deriv->nsteps - 1].__to_name);
193       free ((struct __gconv_step *) deriv->steps);
194     }
195
196   free (deriv);
197 }
198
199
200 /* Decrement the reference count for a single step in a steps array.  */
201 void
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 (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
224 static int
225 gen_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 (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 (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 (current->code->module_name,
325                                        &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 (&result[step_cnt]);
335           free (result);
336           free (from_name);
337           free (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
357 static int
358 increment_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 (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 (&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).  */
411 static int
412 find_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_expand ?: fromset, toset_expand ?: toset,
424                               handle, nsteps);
425   if (result == __GCONV_OK)
426     {
427 #ifndef STATIC_GCONV
428       result = increment_counter (*handle, *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 (solution, toset_expand ?: toset,
657                           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_expand ?: fromset, toset_expand ?: toset,
668                   *handle, *nsteps);
669
670   return result;
671 }
672
673
674 static const char *
675 do_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
686 int
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) != 0)
695     result = strcmp (do_lookup_alias (name1) ?: name1,
696                      do_lookup_alias (name2) ?: name2);
697
698   return result;
699 }
700
701
702 int
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 (fromset);
734   toset_expand = do_lookup_alias (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.  */
767 int
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 (&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.  */
795 static void
796 free_modules_db (struct gconv_module *node)
797 {
798   if (node->left != NULL)
799     free_modules_db (node->left);
800   if (node->right != NULL)
801     free_modules_db (node->right);
802   do
803     {
804       struct gconv_module *act = node;
805       node = node->same;
806       if (act->module_name[0] == '/')
807         free (act);
808     }
809   while (node != NULL);
810 }
811
812
813 /* Free all resources if necessary.  */
814 void
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 (__gconv_modules_db);
831
832   if (known_derivations != NULL)
833     __tdestroy (known_derivations, free_derivation);
834 }