OSDN Git Service

2003-05-28 Jeff Johnston <jjohnstn@redhat.com>
[pf3gnuchains/pf3gnuchains3x.git] / newlib / libc / sys / linux / iconv / gconv_conf.c
1 /* Handle configuration data.
2    Copyright (C) 1997,98,99,2000,2001 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 Lesser General Public
8    License as published by the Free Software Foundation; either
9    version 2.1 of the 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    Lesser General Public License for more details.
15
16    You should have received a copy of the GNU Lesser General Public
17    License along with the GNU C Library; if not, write to the Free
18    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19    02111-1307 USA.  */
20
21 #include <assert.h>
22 #include <ctype.h>
23 #include <errno.h>
24 #include <limits.h>
25 #include <locale.h>
26 #include <search.h>
27 #include <stddef.h>
28 #include <stdio.h>
29 #include <stdio_ext.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33 #include <sys/param.h>
34
35 #include <dirent.h>
36 #include <gconv_int.h>
37
38 /* This is the default path where we look for module lists.  */
39 static const char default_gconv_path[] = GCONV_PATH;
40
41 /* The path elements, as determined by the __gconv_get_path function.
42    All path elements end in a slash.  */
43 struct path_elem *__gconv_path_elem;
44 /* Maximum length of a single path element in __gconv_path_elem.  */
45 size_t __gconv_max_path_elem_len;
46
47 /* We use the following struct if we couldn't allocate memory.  */
48 static const struct path_elem empty_path_elem;
49
50 /* Name of the file containing the module information in the directories
51    along the path.  */
52 static const char gconv_conf_filename[] = "gconv-modules";
53
54 /* Filename extension for the modules.  */
55 #ifndef MODULE_EXT
56 # define MODULE_EXT ".so"
57 #endif
58 static const char gconv_module_ext[] = MODULE_EXT;
59
60 /* We have a few builtin transformations.  */
61 static struct gconv_module builtin_modules[] =
62 {
63 #define BUILTIN_TRANSFORMATION(From, To, Cost, Name, Fct, MinF, MaxF, \
64                                MinT, MaxT) \
65   {                                                                           \
66     from_string: From,                                                        \
67     to_string: To,                                                            \
68     cost_hi: Cost,                                                            \
69     cost_lo: INT_MAX,                                                         \
70     module_name: Name                                                         \
71   },
72 #define BUILTIN_ALIAS(From, To)
73
74 #include "gconv_builtin.h"
75 };
76
77 #undef BUILTIN_TRANSFORMATION
78 #undef BUILTIN_ALIAS
79
80 static const char *builtin_aliases[] =
81 {
82 #define BUILTIN_TRANSFORMATION(From, To, Cost, Name, Fct, MinF, MaxF, \
83                                MinT, MaxT)
84 #define BUILTIN_ALIAS(From, To) From " " To,
85
86 #include "gconv_builtin.h"
87 };
88
89 #ifdef USE_IN_LIBIO
90 # include <libio/libioP.h>
91 # define __getdelim(line, len, c, fp) _IO_getdelim (line, len, c, fp)
92 #endif
93
94
95 /* Value of the GCONV_PATH environment variable.  */
96 const char *__gconv_path_envvar;
97
98
99 /* Test whether there is already a matching module known.  */
100 static int
101 internal_function
102 detect_conflict (const char *alias)
103 {
104   struct gconv_module *node = __gconv_modules_db;
105
106   while (node != NULL)
107     {
108       int cmpres = strcmp (alias, node->from_string);
109
110       if (cmpres == 0)
111         /* We have a conflict.  */
112         return 1;
113       else if (cmpres < 0)
114         node = node->left;
115       else
116         node = node->right;
117     }
118
119   return node != NULL;
120 }
121
122
123 /* Add new alias.  */
124 static inline void
125 add_alias (char *rp, void *modules)
126 {
127   /* We now expect two more string.  The strings are normalized
128      (converted to UPPER case) and strored in the alias database.  */
129   struct gconv_alias *new_alias;
130   char *from, *to, *wp;
131   char old_locale[20], *old_locale_p;
132
133   /* Set locale to default C locale. */
134   old_locale_p = setlocale(LC_ALL, "C");
135   strncpy(old_locale, old_locale_p, 20);
136
137   while (isspace (*rp))
138     ++rp;
139   from = wp = rp;
140   while (*rp != '\0' && !isspace (*rp))
141     *wp++ = toupper (*rp++);
142   if (*rp == '\0')
143     {
144       setlocale(LC_ALL, old_locale);
145       /* There is no `to' string on the line.  Ignore it.  */
146       return;
147     }
148   *wp++ = '\0';
149   to = ++rp;
150   while (isspace (*rp))
151     ++rp;
152   while (*rp != '\0' && !isspace (*rp))
153     *wp++ = toupper (*rp++);
154   if (to == wp)
155     {
156       setlocale(LC_ALL, old_locale);
157       /* No `to' string, ignore the line.  */
158       return;
159     }
160   *wp++ = '\0';
161
162   /* Test whether this alias conflicts with any available module.  */
163   if (detect_conflict (from))
164     {
165       setlocale(LC_ALL, old_locale);
166       /* It does conflict, don't add the alias.  */
167       return;
168     }
169
170   new_alias = (struct gconv_alias *) malloc (sizeof (struct gconv_alias) + (wp - from));
171   if (new_alias != NULL)
172     {
173       void **inserted;
174
175       new_alias->fromname = memcpy ((char *) new_alias
176                                     + sizeof (struct gconv_alias),
177                                     from, wp - from);
178       new_alias->toname = new_alias->fromname + (to - from);
179
180       inserted = (void **) tsearch (new_alias, &__gconv_alias_db,
181                                       __gconv_alias_compare);
182       if (inserted == NULL || *inserted != new_alias)
183         /* Something went wrong, free this entry.  */
184         free (new_alias);
185     }
186   setlocale(LC_ALL, old_locale);
187 }
188
189
190 /* Insert a data structure for a new module in the search tree.  */
191 static inline void
192 internal_function
193 insert_module (struct gconv_module *newp, int tobefreed)
194 {
195   struct gconv_module **rootp = &__gconv_modules_db;
196
197   while (*rootp != NULL)
198     {
199       struct gconv_module *root = *rootp;
200       int cmpres;
201
202       cmpres = strcmp (newp->from_string, root->from_string);
203       if (cmpres == 0)
204         {
205           /* Both strings are identical.  Insert the string at the
206              end of the `same' list if it is not already there.  */
207           while (strcmp (newp->from_string, root->from_string) != 0
208                  || strcmp (newp->to_string, root->to_string) != 0)
209             {
210               rootp = &root->same;
211               root = *rootp;
212               if (root == NULL)
213                 break;
214             }
215
216           if (root != NULL)
217             {
218               /* This is a no new conversion.  But maybe the cost is
219                  better.  */
220               if (newp->cost_hi < root->cost_hi
221                   || (newp->cost_hi == root->cost_hi
222                       && newp->cost_lo < root->cost_lo))
223                 {
224                   newp->left = root->left;
225                   newp->right = root->right;
226                   newp->same = root->same;
227                   *rootp = newp;
228
229                   free (root);
230                 }
231               else if (tobefreed)
232                 free (newp);
233               return;
234             }
235
236           break;
237         }
238       else if (cmpres < 0)
239         rootp = &root->left;
240       else
241         rootp = &root->right;
242     }
243
244   /* Plug in the new node here.  */
245   *rootp = newp;
246 }
247
248
249 /* Add new module.  */
250 static void
251 internal_function
252 add_module (char *rp, const char *directory, size_t dir_len, void **modules,
253             size_t *nmodules, int modcounter)
254 {
255   /* We expect now
256      1. `from' name
257      2. `to' name
258      3. filename of the module
259      4. an optional cost value
260   */
261   struct gconv_alias fake_alias;
262   struct gconv_module *new_module;
263   char *from, *to, *module, *wp;
264   int need_ext;
265   int cost_hi;
266   char old_locale[20], *old_locale_p;
267   char *old;
268   size_t len;
269   char *new;
270
271   /* Set locale to default C locale. */
272   old_locale_p = setlocale(LC_ALL, "C");
273   strncpy(old_locale, old_locale_p, 20);
274
275   while (isspace (*rp))
276     ++rp;
277   from = rp;
278   while (*rp != '\0' && !isspace (*rp))
279     {
280       *rp = toupper (*rp);
281       ++rp;
282     }
283   if (*rp == '\0')
284     {
285       setlocale(LC_ALL, old_locale);
286       return;
287     }
288   *rp++ = '\0';
289   to = wp = rp;
290   while (isspace (*rp))
291     {
292       setlocale(LC_ALL, old_locale);
293       ++rp;
294     }
295   while (*rp != '\0' && !isspace (*rp))
296     *wp++ = toupper (*rp++);
297   if (*rp == '\0')
298     {
299       setlocale(LC_ALL, old_locale);
300       return;
301     }
302   *wp++ = '\0';
303   do
304     ++rp;
305   while (isspace (*rp));
306   module = wp;
307   while (*rp != '\0' && !isspace (*rp))
308     *wp++ = *rp++;
309   if (*rp == '\0')
310     {
311       /* There is no cost, use one by default.  */
312       *wp++ = '\0';
313       cost_hi = 1;
314     }
315   else
316     {
317       /* There might be a cost value.  */
318       char *endp;
319
320       *wp++ = '\0';
321       cost_hi = strtol (rp, &endp, 10);
322       if (rp == endp || cost_hi < 1)
323         /* No useful information.  */
324         cost_hi = 1;
325     }
326
327   if (module[0] == '\0')
328     {
329       setlocale(LC_ALL, old_locale);
330       /* No module name given.  */
331       return;
332     }
333   if (module[0] == '/')
334     dir_len = 0;
335
336   /* See whether we must add the ending.  */
337   need_ext = 0;
338   if (wp - module < (ptrdiff_t) sizeof (gconv_module_ext)
339       || memcmp (wp - sizeof (gconv_module_ext), gconv_module_ext,
340                  sizeof (gconv_module_ext)) != 0)
341     /* We must add the module extension.  */
342     need_ext = sizeof (gconv_module_ext) - 1;
343
344   /* See whether we have already an alias with this name defined.  */
345   old = from;
346   len = strnlen (old, to - from);
347   new = (char *) alloca (len + 1);
348   new[len] = '\0';
349   fake_alias.fromname = (char *) memcpy (new, old, len);
350
351   if (tfind (&fake_alias, &__gconv_alias_db, __gconv_alias_compare) != NULL)
352     {
353       setlocale(LC_ALL, old_locale);
354       /* This module duplicates an alias.  */
355       return;
356     }
357
358   new_module = (struct gconv_module *) calloc (1,
359                                                sizeof (struct gconv_module)
360                                                + (wp - from)
361                                                + dir_len + need_ext);
362   if (new_module != NULL)
363     {
364       char *tmp;
365
366       new_module->from_string = tmp = (char *) (new_module + 1);
367       tmp = memcpy (tmp, from, to - from);
368       tmp += (to - from);
369
370       new_module->to_string = tmp;
371       tmp = memcpy (tmp, to, module - to);
372       tmp += (module - to);
373
374       new_module->cost_hi = cost_hi;
375       new_module->cost_lo = modcounter;
376
377       new_module->module_name = tmp;
378
379       if (dir_len != 0)
380         {
381           tmp = memcpy (tmp, directory, dir_len);
382           tmp += dir_len;
383         }
384
385       tmp = memcpy (tmp, module, wp - module);
386       tmp += (wp - module);
387
388       if (need_ext)
389         memcpy (tmp - 1, gconv_module_ext, sizeof (gconv_module_ext));
390
391       /* Now insert the new module data structure in our search tree.  */
392       insert_module (new_module, 1);
393     }
394   setlocale(LC_ALL, old_locale);
395 }
396
397
398 /* Read the next configuration file.  */
399 static void
400 internal_function
401 read_conf_file (const char *filename, const char *directory, size_t dir_len,
402                 void **modules, size_t *nmodules)
403 {
404   FILE *fp = fopen (filename, "r");
405   char *line = NULL;
406   size_t line_len = 0;
407   static int modcounter;
408   char old_locale[20], *old_locale_p;
409
410   /* Don't complain if a file is not present or readable, simply silently
411      ignore it.  */
412   if (fp == NULL)
413     return;
414
415   /* Set locale to default C locale. */
416   old_locale_p = setlocale(LC_ALL, "C");
417   strncpy(old_locale, old_locale_p, 20);
418
419   /* Process the known entries of the file.  Comments start with `#' and
420      end with the end of the line.  Empty lines are ignored.  */
421   while (!feof (fp))
422     {
423       char *rp, *endp, *word;
424       ssize_t n = __getdelim (&line, &line_len, '\n', fp);
425       if (n < 0)
426         /* An error occurred.  */
427         break;
428
429       rp = line;
430       /* Terminate the line (excluding comments or newline) by an NUL byte
431          to simplify the following code.  */
432       endp = strchr (rp, '#');
433       if (endp != NULL)
434         *endp = '\0';
435       else
436         if (rp[n - 1] == '\n')
437           rp[n - 1] = '\0';
438
439       while (isspace (*rp))
440         ++rp;
441
442       /* If this is an empty line go on with the next one.  */
443       if (rp == endp)
444         continue;
445
446       word = rp;
447       while (*rp != '\0' && !isspace (*rp))
448         ++rp;
449
450       if (rp - word == sizeof ("alias") - 1
451           && memcmp (word, "alias", sizeof ("alias") - 1) == 0)
452         add_alias (rp, *modules);
453       else if (rp - word == sizeof ("module") - 1
454                && memcmp (word, "module", sizeof ("module") - 1) == 0)
455         add_module (rp, directory, dir_len, modules, nmodules, modcounter++);
456       /* else */
457         /* Otherwise ignore the line.  */
458     }
459
460   free (line);
461
462   fclose (fp);
463
464   setlocale(LC_ALL, old_locale);
465 }
466
467
468 /* Determine the directories we are looking for data in.  */
469 void
470 __gconv_get_path (void)
471 {
472   struct path_elem *result;
473   __LOCK_INIT(static, path_lock);
474
475 #ifdef HAVE_DD_LOCK
476   __lock_acquire(path_lock);
477 #endif
478
479   /* Make sure there wasn't a second thread doing it already.  */
480   result = (struct path_elem *) __gconv_path_elem;
481   if (result == NULL)
482     {
483       /* Determine the complete path first.  */
484       char *gconv_path;
485       size_t gconv_path_len;
486       char *elem;
487       char *oldp;
488       char *cp;
489       int nelems;
490       char *cwd;
491       size_t cwdlen;
492
493       if (__gconv_path_envvar == NULL)
494         {
495           char * old = default_gconv_path;
496           size_t len = strlen (old) + 1;
497           char *new = (char *) alloca (len);
498
499           /* No user-defined path.  Make a modifiable copy of the
500              default path.  */
501           gconv_path = (char *) memcpy (new, old, len);
502           gconv_path_len = sizeof (default_gconv_path);
503           cwd = NULL;
504           cwdlen = 0;
505         }
506       else
507         {
508           /* Append the default path to the user-defined path.  */
509           size_t user_len = strlen (__gconv_path_envvar);
510           char *tmp;
511
512           gconv_path_len = user_len + 1 + sizeof (default_gconv_path);
513           gconv_path = alloca (gconv_path_len);
514           tmp = memcpy (gconv_path, __gconv_path_envvar,
515                         user_len);
516           tmp += user_len;
517           memcpy (tmp, ":", 1);
518           tmp += 1;
519           memcpy (tmp,
520                   default_gconv_path, sizeof (default_gconv_path));
521
522           cwd = getcwd (NULL, 0);
523           cwdlen = strlen (cwd);
524         }
525       assert (default_gconv_path[0] == '/');
526
527       /* In a first pass we calculate the number of elements.  */
528       oldp = NULL;
529       cp = strchr (gconv_path, ':');
530       nelems = 1;
531       while (cp != NULL)
532         {
533           if (cp != oldp + 1)
534             ++nelems;
535           oldp = cp;
536           cp =  strchr (cp + 1, ':');
537         }
538
539       /* Allocate the memory for the result.  */
540       result = (struct path_elem *) malloc ((nelems + 1)
541                                             * sizeof (struct path_elem)
542                                             + gconv_path_len + nelems
543                                             + (nelems - 1) * (cwdlen + 1));
544       if (result != NULL)
545         {
546           char *strspace = (char *) &result[nelems + 1];
547           int n = 0;
548
549           /* Separate the individual parts.  */
550           __gconv_max_path_elem_len = 0;
551           elem = strtok_r (gconv_path, ":", &gconv_path);
552           assert (elem != NULL);
553           do
554             {
555               result[n].name = strspace;
556               if (elem[0] != '/')
557                 {
558                   assert (cwd != NULL);
559                   strspace = memcpy (strspace, cwd, cwdlen);
560                   strspace += cwdlen;
561                   *strspace++ = '/';
562                 }
563               strspace = strcpy (strspace, elem);
564               while(*strspace != '\0') strspace++;
565
566               if (strspace[-1] != '/')
567                 *strspace++ = '/';
568
569               result[n].len = strspace - result[n].name;
570               if (result[n].len > __gconv_max_path_elem_len)
571                 __gconv_max_path_elem_len = result[n].len;
572
573               *strspace++ = '\0';
574               ++n;
575             }
576           while ((elem = strtok_r (NULL, ":", &gconv_path)) != NULL);
577
578           result[n].name = NULL;
579           result[n].len = 0;
580         }
581
582       __gconv_path_elem = result ?: (struct path_elem *) &empty_path_elem;
583
584       if (cwd != NULL)
585         free (cwd);
586     }
587
588 #ifdef HAVE_DD_LOCK
589   __lock_release(path_lock);
590 #endif
591 }
592
593
594 /* Read all configuration files found in the user-specified and the default
595    path.  */
596 void
597 __gconv_read_conf (void)
598 {
599   void *modules = NULL;
600   size_t nmodules = 0;
601   int save_errno = errno;
602   size_t cnt;
603   char *filename;
604   char *tmp;
605   const char *elem;
606   size_t elem_len;
607
608   /* First see whether we should use the cache.  */
609   if (__gconv_load_cache () == 0)
610     {
611       /* Yes, we are done.  */
612       __set_errno (save_errno);
613       return;
614     }
615
616 #ifndef STATIC_GCONV
617   /* Find out where we have to look.  */
618   if (__gconv_path_elem == NULL)
619     __gconv_get_path ();
620
621   for (cnt = 0; __gconv_path_elem[cnt].name != NULL; ++cnt)
622     {
623       elem = __gconv_path_elem[cnt].name;
624       elem_len = __gconv_path_elem[cnt].len;
625
626       /* No slash needs to be inserted between elem and gconv_conf_filename;
627          elem already ends in a slash.  */
628       filename = alloca (elem_len + sizeof (gconv_conf_filename));
629       tmp = memcpy (filename, elem, elem_len);
630       tmp += elem_len;
631       memcpy (tmp, gconv_conf_filename, sizeof (gconv_conf_filename));
632
633       /* Read the next configuration file.  */
634       read_conf_file (filename, elem, elem_len, &modules, &nmodules);
635     }
636 #endif
637
638   /* Add the internal modules.  */
639   for (cnt = 0; cnt < sizeof (builtin_modules) / sizeof (builtin_modules[0]);
640        ++cnt)
641     {
642       struct gconv_alias fake_alias;
643
644       fake_alias.fromname = (char *) builtin_modules[cnt].from_string;
645
646       if (tfind (&fake_alias, &__gconv_alias_db, __gconv_alias_compare)
647           != NULL)
648         /* It'll conflict so don't add it.  */
649         continue;
650
651       insert_module (&builtin_modules[cnt], 0);
652     }
653
654   /* Add aliases for builtin conversions.  */
655   cnt = sizeof (builtin_aliases) / sizeof (builtin_aliases[0]);
656   while (cnt > 0)
657     {
658       char * old = builtin_aliases[--cnt];
659       size_t len = strlen (old) + 1;
660       char *new = (char *) alloca (len);
661       char *copy = (char *) memcpy (new, old, len);
662
663       add_alias (copy, modules);
664     }
665
666   /* Restore the error number.  */
667   __set_errno (save_errno);
668 }
669
670
671
672 /* Free all resources if necessary.  */
673 static void __attribute__ ((unused))
674 free_mem (void)
675 {
676   if (__gconv_path_elem != NULL && __gconv_path_elem != &empty_path_elem)
677     free ((void *) __gconv_path_elem);
678 }
679
680 text_set_element (__libc_subfreeres, free_mem);