(find_derivation): Don't allocate memory for
[kopensolaris-gnu/glibc.git] / iconv / gconv_db.c
1 /* Provide access to the collection of available transformation modules.
2    Copyright (C) 1997, 1998 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 Library General Public License as
8    published by the Free Software Foundation; either version 2 of the
9    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    Library General Public License for more details.
15
16    You should have received a copy of the GNU Library General Public
17    License along with the GNU C Library; see the file COPYING.LIB.  If not,
18    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19    Boston, MA 02111-1307, USA.  */
20
21 #include <search.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <bits/libc-lock.h>
25
26 #include <ldsodefs.h>
27 #include <gconv_int.h>
28
29
30 /* Simple data structure for alias mapping.  We have two names, `from'
31    and `to'.  */
32 void *__gconv_alias_db;
33
34 /* Array with available modules.  */
35 size_t __gconv_nmodules;
36 struct gconv_module **__gconv_modules_db;
37
38 /* We modify global data.   */
39 __libc_lock_define_initialized (static, lock)
40
41
42 /* Function for searching alias.  */
43 int
44 __gconv_alias_compare (const void *p1, const void *p2)
45 {
46   struct gconv_alias *s1 = (struct gconv_alias *) p1;
47   struct gconv_alias *s2 = (struct gconv_alias *) p2;
48   return __strcasecmp (s1->fromname, s2->fromname);
49 }
50
51
52 /* To search for a derivation we create a list of intermediate steps.
53    Each element contains a pointer to the element which precedes it
54    in the derivation order.  */
55 struct derivation_step
56 {
57   const char *result_set;
58   struct gconv_module *code;
59   struct derivation_step *last;
60   struct derivation_step *next;
61 };
62
63 #define NEW_STEP(result, module, last_mod) \
64   ({ struct derivation_step *newp = alloca (sizeof (struct derivation_step)); \
65      newp->result_set = result;                                               \
66      newp->code = module;                                                     \
67      newp->last = last_mod;                                                   \
68      newp->next = NULL;                                                       \
69      newp; })
70
71
72 /* If a specific transformation is used more than once we should not need
73    to start looking for it again.  Instead cache each successful result.  */
74 struct known_derivation
75 {
76   const char *from;
77   const char *to;
78   struct gconv_step *steps;
79   size_t nsteps;
80 };
81
82 /* Compare function for database of found derivations.  */
83 static int
84 derivation_compare (const void *p1, const void *p2)
85 {
86   struct known_derivation *s1 = (struct known_derivation *) p1;
87   struct known_derivation *s2 = (struct known_derivation *) p2;
88   int result;
89
90   result = strcmp (s1->from, s2->from);
91   if (result == 0)
92     result = strcmp (s1->to, s2->to);
93   return result;
94 }
95
96 /* The search tree for known derivations.  */
97 static void *known_derivations;
98
99 /* Look up whether given transformation was already requested before.  */
100 static int
101 internal_function
102 derivation_lookup (const char *fromset, const char *toset,
103                    struct gconv_step **handle, size_t *nsteps)
104 {
105   struct known_derivation key = { fromset, toset, NULL, 0 };
106   struct known_derivation **result;
107
108   result = __tfind (&key, &known_derivations, derivation_compare);
109
110   if (result == NULL)
111     return GCONV_NOCONV;
112
113   *handle = (*result)->steps;
114   *nsteps = (*result)->nsteps;
115
116   /* Please note that we return GCONV_OK even if the last search for
117      this transformation was unsuccessful.  */
118   return GCONV_OK;
119 }
120
121 /* Add new derivation to list of known ones.  */
122 static void
123 internal_function
124 add_derivation (const char *fromset, const char *toset,
125                 struct gconv_step *handle, size_t nsteps)
126 {
127   struct known_derivation *new_deriv;
128   size_t fromset_len = strlen (fromset) + 1;
129   size_t toset_len = strlen (toset) + 1;
130
131   new_deriv = (struct known_derivation *)
132     malloc (sizeof (struct known_derivation) + fromset_len + toset_len);
133   if (new_deriv != NULL)
134     {
135       new_deriv->from = memcpy (new_deriv + 1, fromset, fromset_len);
136       new_deriv->to = memcpy ((char *) new_deriv->from + fromset_len,
137                               toset, toset_len);
138
139       new_deriv->steps = handle;
140       new_deriv->nsteps = nsteps;
141
142       __tsearch (new_deriv, &known_derivations, derivation_compare);
143     }
144   /* Please note that we don't complain if the allocation failed.  This
145      is not tragically but in case we use the memory debugging facilities
146      not all memory will be freed.  */
147 }
148
149 static void
150 free_derivation (void *p)
151 {
152   struct known_derivation *deriv = (struct known_derivation *) p;
153   size_t cnt;
154
155   for (cnt = 0; cnt < deriv->nsteps; ++cnt)
156     if (deriv->steps[cnt].end_fct)
157       _CALL_DL_FCT (deriv->steps[cnt].end_fct, (&deriv->steps[cnt]));
158
159   free ((struct gconv_step *) deriv->steps);
160   free (deriv);
161 }
162
163
164 static int
165 internal_function
166 gen_steps (struct derivation_step *best, const char *toset,
167            const char *fromset, struct gconv_step **handle, size_t *nsteps)
168 {
169   size_t step_cnt = 0;
170   struct gconv_step *result;
171   struct derivation_step *current;
172   int status = GCONV_NOMEM;
173
174   /* First determine number of steps.  */
175   for (current = best; current->last != NULL; current = current->last)
176     ++step_cnt;
177
178   result = (struct gconv_step *) malloc (sizeof (struct gconv_step)
179                                          * step_cnt);
180   if (result != NULL)
181     {
182       int failed = 0;
183
184       status = GCONV_OK;
185       *nsteps = step_cnt;
186       current = best;
187       while (step_cnt-- > 0)
188         {
189           result[step_cnt].from_name = (step_cnt == 0
190                                         ? __strdup (fromset)
191                                         : current->last->result_set);
192           result[step_cnt].to_name = (step_cnt + 1 == *nsteps
193                                       ? __strdup (current->result_set)
194                                       : result[step_cnt + 1].from_name);
195
196 #ifndef STATIC_GCONV
197           if (current->code->module_name[0] == '/')
198             {
199               /* Load the module, return handle for it.  */
200               struct gconv_loaded_object *shlib_handle =
201                 __gconv_find_shlib (current->code->module_name);
202
203               if (shlib_handle == NULL)
204                 {
205                   failed = 1;
206                   break;
207                 }
208
209               result[step_cnt].shlib_handle = shlib_handle;
210               result[step_cnt].modname = shlib_handle->name;
211               result[step_cnt].counter = 0;
212               result[step_cnt].fct = shlib_handle->fct;
213               result[step_cnt].init_fct = shlib_handle->init_fct;
214               result[step_cnt].end_fct = shlib_handle->end_fct;
215             }
216           else
217 #endif
218             /* It's a builtin transformation.  */
219             __gconv_get_builtin_trans (current->code->module_name,
220                                        &result[step_cnt]);
221
222           /* Call the init function.  */
223           if (result[step_cnt].init_fct != NULL)
224              {
225                status = _CALL_DL_FCT (result[step_cnt].init_fct,
226                                       (&result[step_cnt]));
227                
228                if (status != GCONV_OK)
229                  {
230                    failed = 1;
231                    /* Make sure we unload this modules.  */
232                    --step_cnt;
233                    break;
234                  }
235              }
236
237           current = current->last;
238         }
239
240       if (failed != 0)
241         {
242           /* Something went wrong while initializing the modules.  */
243           while (++step_cnt < *nsteps)
244             {
245               if (result[step_cnt].end_fct != NULL)
246                 _CALL_DL_FCT (result[step_cnt].end_fct, (&result[step_cnt]));
247 #ifndef STATIC_GCONV
248               __gconv_release_shlib (result[step_cnt].shlib_handle);
249 #endif
250             }
251           free (result);
252           *nsteps = 0;
253           *handle = NULL;
254           if (status == GCONV_OK)
255             status = GCONV_NOCONV;
256         }
257       else
258         *handle = result;
259     }
260   else
261     {
262       *nsteps = 0;
263       *handle = NULL;
264     }
265
266   return status;
267 }
268
269
270 /* The main function: find a possible derivation from the `fromset' (either
271    the given name or the alias) to the `toset' (again with alias).  */
272 static int
273 internal_function
274 find_derivation (const char *toset, const char *toset_expand,
275                  const char *fromset, const char *fromset_expand,
276                  struct gconv_step **handle, size_t *nsteps)
277 {
278   __libc_lock_define_initialized (static, lock)
279   struct derivation_step *first, *current, **lastp, *best = NULL;
280   int best_cost_hi = 0;
281   int best_cost_lo = 0;
282   int result;
283
284   result = derivation_lookup (fromset_expand ?: fromset, toset_expand ?: toset,
285                               handle, nsteps);
286   if (result == GCONV_OK)
287     return result;
288
289   __libc_lock_lock (lock);
290
291   /* There is a small chance that this derivation is meanwhile found.  This
292      can happen if in `find_derivation' we look for this derivation, didn't
293      find it but at the same time another thread looked for this derivation. */
294   result = derivation_lookup (fromset_expand ?: fromset, toset_expand ?: toset,
295                               handle, nsteps);
296   if (result == GCONV_OK)
297     return result;
298
299   /* ### TODO
300      For now we use a simple algorithm with quadratic runtime behaviour.
301      The task is to match the `toset' with any of the available rules,
302      starting from FROMSET.  */
303   if (fromset_expand != NULL)
304     {
305       first = NEW_STEP (fromset_expand, NULL, NULL);
306       first->next = NEW_STEP (fromset, NULL, NULL);
307       lastp = &first->next->next;
308     }
309   else
310     {
311       first = NEW_STEP (fromset, NULL, NULL);
312       lastp = &first->next;
313     }
314
315   current = first;
316   while (current != NULL)
317     {
318       /* Now match all the available module specifications against the
319          current charset name.  If any of them matches check whether
320          we already have a derivation for this charset.  If yes, use the
321          one with the lower costs.  Otherwise add the new charset at the
322          end.  */
323       size_t cnt;
324
325       for (cnt = 0; cnt < __gconv_nmodules; ++cnt)
326         {
327           const char *result_set = NULL;
328
329           if (__gconv_modules_db[cnt]->from_pattern == NULL)
330             {
331               if (__strcasecmp (current->result_set,
332                                 __gconv_modules_db[cnt]->from_constpfx) == 0)
333                 {
334                   if (strcmp (__gconv_modules_db[cnt]->to_string, "-") == 0)
335                     result_set = toset_expand ?: toset;
336                   else
337                     result_set = __gconv_modules_db[cnt]->to_string;
338                 }
339             }
340           else
341             /* We have a regular expression.  First see if the prefix
342                matches.  */
343             if (__strncasecmp (current->result_set,
344                                __gconv_modules_db[cnt]->from_constpfx,
345                                __gconv_modules_db[cnt]->from_constpfx_len)
346                 == 0)
347               {
348                 /* First compile the regex if not already done.  */
349                 if (__gconv_modules_db[cnt]->from_regex == NULL)
350                   {
351                     if (__regcomp (&__gconv_modules_db[cnt]->from_regex_mem,
352                                    __gconv_modules_db[cnt]->from_pattern,
353                                    REG_EXTENDED | REG_ICASE) != 0)
354                       /* Something is wrong.  Remember this.  */
355                       __gconv_modules_db[cnt]->from_regex = (regex_t *) -1L;
356                     else
357                       __gconv_modules_db[cnt]->from_regex
358                         = &__gconv_modules_db[cnt]->from_regex_mem;
359                   }
360
361                 if (__gconv_modules_db[cnt]->from_regex != (regex_t *) -1L)
362                   {
363                     /* Try to match the from name.  */
364                     regmatch_t match[4];
365
366                     if (__regexec (__gconv_modules_db[cnt]->from_regex,
367                                    current->result_set, 4, match, 0) == 0
368                         && match[0].rm_so == 0
369                         && current->result_set[match[0].rm_eo] == '\0')
370                       {
371                         /* At least the whole <from> string is matched.
372                            We must now match sed-like possible
373                            subexpressions from the match to the
374                            toset expression.  */
375 #define ENSURE_LEN(LEN) \
376   if (wp + (LEN) >= constr + len - 1)                                         \
377     {                                                                         \
378       char *newp = alloca (len += 128);                                       \
379       memcpy (newp, constr, wp - constr);                                     \
380       wp = newp + (wp - constr);                                              \
381       constr = newp;                                                          \
382     }
383                         size_t len = 128;
384                         char *constr = alloca (len);
385                         char *wp = constr;
386                         const char *cp = __gconv_modules_db[cnt]->to_string;
387
388                         while (*cp != '\0')
389                           {
390                             if (*cp != '\\')
391                               {
392                                 ENSURE_LEN (1);
393                                 *wp++ = *cp++;
394                               }
395                             else if (cp[1] == '\0')
396                               /* Backslash at end of string.  */
397                               break;
398                             else
399                               {
400                                 ++cp;
401                                 if (*cp == '\\')
402                                   {
403                                     *wp++ = *cp++;
404                                     ENSURE_LEN (1);
405                                   }
406                                 else if (*cp < '1' || *cp > '3')
407                                   break;
408                                 else
409                                   {
410                                     int idx = *cp - '0';
411                                     if (match[idx].rm_so == -1)
412                                       /* No match.  */
413                                       break;
414
415                                     ENSURE_LEN (match[idx].rm_eo
416                                                 - match[idx].rm_so);
417                                     wp = __mempcpy (wp,
418                                                     &current->result_set[match[idx].rm_so],
419                                                     match[idx].rm_eo
420                                                     - match[idx].rm_so);
421                                     ++cp;
422                                   }
423                               }
424                           }
425                         if (*cp == '\0' && wp != constr)
426                           {
427                                 /* Terminate the constructed string.  */
428                             *wp = '\0';
429                             result_set = constr;
430                           }
431                       }
432                   }
433               }
434
435           if (result_set != NULL)
436             {
437               /* We managed to find a derivation.  First see whether
438                  this is what we are looking for.  */
439               if (__strcasecmp (result_set, toset) == 0
440                   || (toset_expand != NULL
441                       && __strcasecmp (result_set, toset_expand) == 0))
442                 {
443                   /* Determine the costs.  If they are lower than the
444                      previous solution (or this is the first solution)
445                      remember this solution.  */
446                   int cost_hi = __gconv_modules_db[cnt]->cost_hi;
447                   int cost_lo = __gconv_modules_db[cnt]->cost_lo;
448                   struct derivation_step *runp = current;
449                   while (runp->code != NULL)
450                     {
451                       cost_hi += runp->code->cost_hi;
452                       cost_lo += runp->code->cost_lo;
453                       runp = runp->last;
454                     }
455                   if (best == NULL || cost_hi < best_cost_hi
456                       || (cost_hi == best_cost_hi && cost_lo < best_cost_lo))
457                     {
458                       best = NEW_STEP (result_set, __gconv_modules_db[cnt],
459                                        current);
460                       best_cost_hi = cost_hi;
461                       best_cost_lo = cost_lo;
462                     }
463                 }
464               else
465                 {
466                   /* Append at the end if there is no entry with this name.  */
467                   struct derivation_step *runp = first;
468
469                   while (runp != NULL)
470                     {
471                       if (__strcasecmp (result_set, runp->result_set) == 0)
472                         break;
473                       runp = runp->next;
474                     }
475
476                   if (runp == NULL)
477                     {
478                       *lastp = NEW_STEP (result_set, __gconv_modules_db[cnt],
479                                          current);
480                       lastp = &(*lastp)->next;
481                     }
482                 }
483             }
484         }
485
486       /* Go on with the next entry.  */
487       current = current->next;
488     }
489
490   if (best != NULL)
491     /* We really found a way to do the transformation.  Now build a data
492        structure describing the transformation steps.*/
493     result = gen_steps (best, toset_expand ?: toset, fromset_expand ?: fromset,
494                         handle, nsteps);
495   else
496     {
497       /* We haven't found a transformation.  Clear the result values.  */
498       *handle = NULL;
499       *nsteps = 0;
500     }
501
502   /* Add result in any case to list of known derivations.  */
503   add_derivation (fromset_expand ?: fromset, toset_expand ?: toset,
504                   *handle, *nsteps);
505
506   __libc_lock_unlock (lock);
507
508   return result;
509 }
510
511
512 int
513 internal_function
514 __gconv_find_transform (const char *toset, const char *fromset,
515                         struct gconv_step **handle, size_t *nsteps)
516 {
517   __libc_once_define (static, once);
518   const char *fromset_expand = NULL;
519   const char *toset_expand = NULL;
520   int result;
521
522   /* Ensure that the configuration data is read.  */
523   __libc_once (once, __gconv_read_conf);
524
525   /* Acquire the lock.  */
526   __libc_lock_lock (lock);
527
528   /* If we don't have a module database return with an error.  */
529   if (__gconv_modules_db == NULL)
530     return GCONV_NOCONV;
531
532   /* See whether the names are aliases.  */
533   if (__gconv_alias_db != NULL)
534     {
535       struct gconv_alias key;
536       struct gconv_alias **found;
537
538       key.fromname = fromset;
539       found = __tfind (&key, &__gconv_alias_db, __gconv_alias_compare);
540       fromset_expand = found != NULL ? (*found)->toname : NULL;
541
542       key.fromname = toset;
543       found = __tfind (&key, &__gconv_alias_db, __gconv_alias_compare);
544       toset_expand = found != NULL ? (*found)->toname : NULL;
545     }
546
547   result = find_derivation (toset, toset_expand, fromset, fromset_expand,
548                             handle, nsteps);
549
550 #ifndef STATIC_GCONV
551   /* Increment the user counter.  */
552   if (result == GCONV_OK)
553     {
554       size_t cnt = *nsteps;
555       struct gconv_step *steps = *handle;
556
557       do
558         if (steps[--cnt].counter++ == 0)
559           {
560             steps[cnt].shlib_handle =
561               __gconv_find_shlib (steps[cnt].modname);
562             if (steps[cnt].shlib_handle == NULL)
563               {
564                 /* Oops, this is the second time we use this module (after
565                    unloading) and this time loading failed!?  */
566                 while (++cnt < *nsteps)
567                   __gconv_release_shlib (steps[cnt].shlib_handle);
568                 result = GCONV_NOCONV;
569                 break;
570               }
571           }
572       while (cnt > 0);
573     }
574 #endif
575
576   /* Release the lock.  */
577   __libc_lock_unlock (lock);
578
579   /* The following code is necessary since `find_derivation' will return
580      GCONV_OK even when no derivation was found but the same request
581      was processed before.  I.e., negative results will also be cached.  */
582   return (result == GCONV_OK
583           ? (*handle == NULL ? GCONV_NOCONV : GCONV_OK)
584           : result);
585 }
586
587
588 /* Release the entries of the modules list.  */
589 int
590 internal_function
591 __gconv_close_transform (struct gconv_step *steps, size_t nsteps)
592 {
593   int result = GCONV_OK;
594
595 #ifndef STATIC_GCONV
596   /* Acquire the lock.  */
597   __libc_lock_lock (lock);
598
599   while (nsteps-- > 0)
600     if (steps[nsteps].shlib_handle != NULL
601         && --steps[nsteps].counter == 0)
602       {
603         result = __gconv_release_shlib (steps[nsteps].shlib_handle);
604         if (result != GCONV_OK)
605           break;
606         steps[nsteps].shlib_handle = NULL;
607       }
608
609   /* Release the lock.  */
610   __libc_lock_unlock (lock);
611 #endif
612
613   return result;
614 }
615
616
617 /* Free all resources if necessary.  */
618 static void __attribute__ ((unused))
619 free_mem (void)
620 {
621   size_t cnt;
622
623   if (__gconv_alias_db != NULL)
624     __tdestroy (__gconv_alias_db, free);
625
626   for (cnt = 0; cnt < __gconv_nmodules; ++cnt)
627     /* Modules which names do not start with a slash are builtin
628        transformations and the memory is not allocated dynamically.  */
629     if (__gconv_modules_db[cnt]->module_name[0] == '/')
630       free (__gconv_modules_db[cnt]);
631
632   if (known_derivations != NULL)
633     __tdestroy (known_derivations, free_derivation);
634 }
635
636 text_set_element (__libc_subfreeres, free_mem);