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