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