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