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