Update.
[platform/upstream/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 #include <elf/ldsodefs.h>
26
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 internal_function
151 free_derivation (void *p)
152 {
153   struct known_derivation *deriv = (struct known_derivation *) p;
154   size_t cnt;
155
156   for (cnt = 0; cnt < deriv->nsteps; ++cnt)
157     if (deriv->steps[cnt].end_fct)
158       _CALL_DL_FCT (deriv->steps[cnt].end_fct, (&deriv->steps[cnt]));
159
160   free ((struct gconv_step *) deriv->steps);
161   free (deriv);
162 }
163
164
165 static int
166 internal_function
167 gen_steps (struct derivation_step *best, const char *toset,
168            const char *fromset, struct gconv_step **handle, size_t *nsteps)
169 {
170   size_t step_cnt = 0;
171   struct gconv_step *result;
172   struct derivation_step *current;
173   int status = GCONV_NOMEM;
174
175   /* First determine number of steps.  */
176   for (current = best; current->last != NULL; current = current->last)
177     ++step_cnt;
178
179   result = (struct gconv_step *) malloc (sizeof (struct gconv_step)
180                                          * step_cnt);
181   if (result != NULL)
182     {
183       int failed = 0;
184
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             _CALL_DL_FCT (result[step_cnt].init_fct, (&result[step_cnt]));
225
226           current = current->last;
227         }
228
229       if (failed != 0)
230         {
231           /* Something went wrong while initializing the modules.  */
232           while (++step_cnt < *nsteps)
233             {
234               if (result[step_cnt].end_fct != NULL)
235                 _CALL_DL_FCT (result[step_cnt].end_fct, (&result[step_cnt]));
236 #ifndef STATIC_GCONV
237               __gconv_release_shlib (result[step_cnt].shlib_handle);
238 #endif
239             }
240           free (result);
241           *nsteps = 0;
242           *handle = NULL;
243           status = GCONV_NOCONV;
244         }
245       else
246         {
247           *handle = result;
248           status = GCONV_OK;
249         }
250     }
251   else
252     {
253       *nsteps = 0;
254       *handle = NULL;
255     }
256
257   return status;
258 }
259
260
261 /* The main function: find a possible derivation from the `fromset' (either
262    the given name or the alias) to the `toset' (again with alias).  */
263 static int
264 internal_function
265 find_derivation (const char *toset, const char *toset_expand,
266                  const char *fromset, const char *fromset_expand,
267                  struct gconv_step **handle, size_t *nsteps)
268 {
269   __libc_lock_define_initialized (static, lock)
270   struct derivation_step *first, *current, **lastp, *best = NULL;
271   int best_cost_hi = 0;
272   int best_cost_lo = 0;
273   int result;
274
275   result = derivation_lookup (fromset_expand ?: fromset, toset_expand ?: toset,
276                               handle, nsteps);
277   if (result == GCONV_OK)
278     return result;
279
280   __libc_lock_lock (lock);
281
282   /* There is a small chance that this derivation is meanwhile found.  This
283      can happen if in `find_derivation' we look for this derivation, didn't
284      find it but at the same time another thread looked for this derivation. */
285   result = derivation_lookup (fromset_expand ?: fromset, toset_expand ?: toset,
286                               handle, nsteps);
287   if (result == GCONV_OK)
288     return result;
289
290   /* ### TODO
291      For now we use a simple algorithm with quadratic runtime behaviour.
292      The task is to match the `toset' with any of the available rules,
293      starting from FROMSET.  */
294   if (fromset_expand != NULL)
295     {
296       first = NEW_STEP (fromset_expand, NULL, NULL);
297       first->next = NEW_STEP (fromset, NULL, NULL);
298       lastp = &first->next->next;
299     }
300   else
301     {
302       first = NEW_STEP (fromset, NULL, NULL);
303       lastp = &first->next;
304     }
305
306   current = first;
307   while (current != NULL)
308     {
309       /* Now match all the available module specifications against the
310          current charset name.  If any of them matches check whether
311          we already have a derivation for this charset.  If yes, use the
312          one with the lower costs.  Otherwise add the new charset at the
313          end.  */
314       size_t cnt;
315
316       for (cnt = 0; cnt < __gconv_nmodules; ++cnt)
317         {
318           const char *result_set = NULL;
319
320           if (__gconv_modules_db[cnt]->from_pattern == NULL)
321             {
322               if (__strcasecmp (current->result_set,
323                                 __gconv_modules_db[cnt]->from_constpfx) == 0)
324                 {
325                   if (strcmp (__gconv_modules_db[cnt]->to_string, "-") == 0)
326                     result_set = toset_expand ?: toset;
327                   else
328                     result_set = __gconv_modules_db[cnt]->to_string;
329                 }
330             }
331           else
332             /* We have a regular expression.  First see if the prefix
333                matches.  */
334             if (__strncasecmp (current->result_set,
335                                __gconv_modules_db[cnt]->from_constpfx,
336                                __gconv_modules_db[cnt]->from_constpfx_len)
337                 == 0)
338               {
339                 /* First compile the regex if not already done.  */
340                 if (__gconv_modules_db[cnt]->from_regex == NULL)
341                   {
342                     regex_t *newp = (regex_t *) malloc (sizeof (regex_t));
343
344                     if (__regcomp (newp, __gconv_modules_db[cnt]->from_pattern,
345                                    REG_EXTENDED | REG_ICASE) != 0)
346                       {
347                         /* Something is wrong.  Remember this.  */
348                         free (newp);
349                         __gconv_modules_db[cnt]->from_regex = (regex_t *) -1L;
350                       }
351                     else
352                       __gconv_modules_db[cnt]->from_regex = newp;
353                   }
354
355                 if (__gconv_modules_db[cnt]->from_regex != (regex_t *) -1L)
356                   {
357                     /* Try to match the from name.  */
358                     regmatch_t match[4];
359
360                     if (__regexec (__gconv_modules_db[cnt]->from_regex,
361                                    current->result_set, 4, match, 0) == 0
362                         && match[0].rm_so == 0
363                         && current->result_set[match[0].rm_eo] == '\0')
364                       {
365                         /* At least the whole <from> string is matched.
366                            We must now match sed-like possible
367                            subexpressions from the match to the
368                            toset expression.  */
369 #define ENSURE_LEN(LEN) \
370   if (wp + (LEN) >= constr + len - 1)                                         \
371     {                                                                         \
372       char *newp = alloca (len += 128);                                       \
373       memcpy (newp, constr, wp - constr);                                     \
374       wp = newp + (wp - constr);                                              \
375       constr = newp;                                                          \
376     }
377                         size_t len = 128;
378                         char *constr = alloca (len);
379                         char *wp = constr;
380                         const char *cp = __gconv_modules_db[cnt]->to_string;
381
382                         while (*cp != '\0')
383                           {
384                             if (*cp != '\\')
385                               {
386                                 ENSURE_LEN (1);
387                                 *wp++ = *cp++;
388                               }
389                             else if (cp[1] == '\0')
390                               /* Backslash at end of string.  */
391                               break;
392                             else
393                               {
394                                 ++cp;
395                                 if (*cp == '\\')
396                                   {
397                                     *wp++ = *cp++;
398                                     ENSURE_LEN (1);
399                                   }
400                                 else if (*cp < '1' || *cp > '3')
401                                   break;
402                                 else
403                                   {
404                                     int idx = *cp - '0';
405                                     if (match[idx].rm_so == -1)
406                                       /* No match.  */
407                                       break;
408
409                                     ENSURE_LEN (match[idx].rm_eo
410                                                 - match[idx].rm_so);
411                                     wp = __mempcpy (wp,
412                                                     &current->result_set[match[idx].rm_so],
413                                                     match[idx].rm_eo
414                                                     - match[idx].rm_so);
415                                     ++cp;
416                                   }
417                               }
418                           }
419                         if (*cp == '\0' && wp != constr)
420                           {
421                                 /* Terminate the constructed string.  */
422                             *wp = '\0';
423                             result_set = constr;
424                           }
425                       }
426                   }
427               }
428
429           if (result_set != NULL)
430             {
431               /* We managed to find a derivation.  First see whether
432                  this is what we are looking for.  */
433               if (__strcasecmp (result_set, toset) == 0
434                   || (toset_expand != NULL
435                       && __strcasecmp (result_set, toset_expand) == 0))
436                 {
437                   /* Determine the costs.  If they are lower than the
438                      previous solution (or this is the first solution)
439                      remember this solution.  */
440                   int cost_hi = __gconv_modules_db[cnt]->cost_hi;
441                   int cost_lo = __gconv_modules_db[cnt]->cost_lo;
442                   struct derivation_step *runp = current;
443                   while (runp->code != NULL)
444                     {
445                       cost_hi += runp->code->cost_hi;
446                       cost_lo += runp->code->cost_lo;
447                       runp = runp->last;
448                     }
449                   if (best == NULL || cost_hi < best_cost_hi
450                       || (cost_hi == best_cost_hi && cost_lo < best_cost_lo))
451                     {
452                       best = NEW_STEP (result_set, __gconv_modules_db[cnt],
453                                        current);
454                       best_cost_hi = cost_hi;
455                       best_cost_lo = cost_lo;
456                     }
457                 }
458               else
459                 {
460                   /* Append at the end if there is no entry with this name.  */
461                   struct derivation_step *runp = first;
462
463                   while (runp != NULL)
464                     {
465                       if (__strcasecmp (result_set, runp->result_set) == 0)
466                         break;
467                       runp = runp->next;
468                     }
469
470                   if (runp == NULL)
471                     {
472                       *lastp = NEW_STEP (result_set, __gconv_modules_db[cnt],
473                                          current);
474                       lastp = &(*lastp)->next;
475                     }
476                 }
477             }
478         }
479
480       /* Go on with the next entry.  */
481       current = current->next;
482     }
483
484   if (best != NULL)
485     /* We really found a way to do the transformation.  Now build a data
486        structure describing the transformation steps.*/
487     result = gen_steps (best, toset_expand ?: toset, fromset_expand ?: fromset,
488                         handle, nsteps);
489   else
490     {
491       /* We haven't found a transformation.  Clear the result values.  */
492       *handle = NULL;
493       *nsteps = 0;
494     }
495
496   /* Add result in any case to list of known derivations.  */
497   add_derivation (fromset_expand ?: fromset, toset_expand ?: toset,
498                   *handle, *nsteps);
499
500   __libc_lock_unlock (lock);
501
502   return result;
503 }
504
505
506 int
507 internal_function
508 __gconv_find_transform (const char *toset, const char *fromset,
509                         struct gconv_step **handle, size_t *nsteps)
510 {
511   __libc_once_define (static, once);
512   const char *fromset_expand = NULL;
513   const char *toset_expand = NULL;
514   int result;
515
516   /* Ensure that the configuration data is read.  */
517   __libc_once (once, __gconv_read_conf);
518
519   /* Acquire the lock.  */
520   __libc_lock_lock (lock);
521
522   /* If we don't have a module database return with an error.  */
523   if (__gconv_modules_db == NULL)
524     return GCONV_NOCONV;
525
526   /* See whether the names are aliases.  */
527   if (__gconv_alias_db != NULL)
528     {
529       struct gconv_alias key;
530       struct gconv_alias **found;
531
532       key.fromname = fromset;
533       found = __tfind (&key, &__gconv_alias_db, __gconv_alias_compare);
534       fromset_expand = found != NULL ? (*found)->toname : NULL;
535
536       key.fromname = toset;
537       found = __tfind (&key, &__gconv_alias_db, __gconv_alias_compare);
538       toset_expand = found != NULL ? (*found)->toname : NULL;
539     }
540
541   result = find_derivation (toset, toset_expand, fromset, fromset_expand,
542                             handle, nsteps);
543
544 #ifndef STATIC_GCONV
545   /* Increment the user counter.  */
546   if (result == GCONV_OK)
547     {
548       size_t cnt = *nsteps;
549       struct gconv_step *steps = *handle;
550
551       do
552         if (steps[--cnt].counter++ == 0)
553           {
554             steps[cnt].shlib_handle =
555               __gconv_find_shlib (steps[cnt].modname);
556             if (steps[cnt].shlib_handle == NULL)
557               {
558                 /* Oops, this is the second time we use this module (after
559                    unloading) and this time loading failed!?  */
560                 while (++cnt < *nsteps)
561                   __gconv_release_shlib (steps[cnt].shlib_handle);
562                 result = GCONV_NOCONV;
563                 break;
564               }
565           }
566       while (cnt > 0);
567     }
568 #endif
569
570   /* Release the lock.  */
571   __libc_lock_unlock (lock);
572
573   /* The following code is necessary since `find_derivation' will return
574      GCONV_OK even when no derivation was found but the same request
575      was processed before.  I.e., negative results will also be cached.  */
576   return (result == GCONV_OK
577           ? (*handle == NULL ? GCONV_NOCONV : GCONV_OK)
578           : result);
579 }
580
581
582 /* Release the entries of the modules list.  */
583 int
584 internal_function
585 __gconv_close_transform (struct gconv_step *steps, size_t nsteps)
586 {
587   int result = GCONV_OK;
588
589 #ifndef STATIC_GCONV
590   /* Acquire the lock.  */
591   __libc_lock_lock (lock);
592
593   while (nsteps-- > 0)
594     if (steps[nsteps].shlib_handle != NULL
595         && --steps[nsteps].counter == 0)
596       {
597         result = __gconv_release_shlib (steps[nsteps].shlib_handle);
598         if (result != GCONV_OK)
599           break;
600         steps[nsteps].shlib_handle = NULL;
601       }
602
603   /* Release the lock.  */
604   __libc_lock_unlock (lock);
605 #endif
606
607   return result;
608 }
609
610
611 /* Free all resources if necessary.  */
612 static void __attribute__ ((unused))
613 free_mem (void)
614 {
615   size_t cnt;
616
617   if (__gconv_alias_db != NULL)
618     __tdestroy (__gconv_alias_db, free);
619
620   for (cnt = 0; cnt < __gconv_nmodules; ++cnt)
621     {
622       if (__gconv_modules_db[cnt]->from_regex != NULL)
623         __regfree ((regex_t *) __gconv_modules_db[cnt]->from_regex);
624
625       /* Modules which names do not start with a slash are builtin
626          transformations and the memory is not allocated dynamically.  */
627       if (__gconv_modules_db[cnt]->module_name[0] == '/')
628         free (__gconv_modules_db[cnt]);
629     }
630
631   if (known_derivations != NULL)
632     __tdestroy (known_derivations, free_derivation);
633 }
634
635 text_set_element (__libc_subfreeres, free_mem);