make toolbar refresh smoother
[claws.git] / intl / localealias.c
1 /* Handle aliases for locale names.
2    Copyright (C) 1995-1999, 2000, 2001 Free Software Foundation, Inc.
3
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2, or (at your option)
7    any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software Foundation,
16    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
17
18 /* Tell glibc's <string.h> to provide a prototype for mempcpy().
19    This must come before <config.h> because <config.h> may include
20    <features.h>, and once <features.h> has been included, it's too late.  */
21 #ifndef _GNU_SOURCE
22 # define _GNU_SOURCE    1
23 #endif
24
25 #ifdef HAVE_CONFIG_H
26 # include <config.h>
27 #endif
28
29 #include <ctype.h>
30 #include <stdio.h>
31 #include <sys/types.h>
32
33 #ifdef __GNUC__
34 # define alloca __builtin_alloca
35 # define HAVE_ALLOCA 1
36 #else
37 # if defined HAVE_ALLOCA_H || defined _LIBC
38 #  include <alloca.h>
39 # else
40 #  ifdef _AIX
41  #pragma alloca
42 #  else
43 #   ifndef alloca
44 char *alloca ();
45 #   endif
46 #  endif
47 # endif
48 #endif
49
50 #include <stdlib.h>
51
52 #include <string.h>
53 #if !HAVE_STRCHR && !defined _LIBC
54 # ifndef strchr
55 #  define strchr index
56 # endif
57 #endif
58
59 #include "gettextP.h"
60
61 /* @@ end of prolog @@ */
62
63 #ifdef _LIBC
64 /* Rename the non ANSI C functions.  This is required by the standard
65    because some ANSI C functions will require linking with this object
66    file and the name space must not be polluted.  */
67 # define strcasecmp __strcasecmp
68
69 # ifndef mempcpy
70 #  define mempcpy __mempcpy
71 # endif
72 # define HAVE_MEMPCPY   1
73
74 /* We need locking here since we can be called from different places.  */
75 # include <bits/libc-lock.h>
76
77 __libc_lock_define_initialized (static, lock);
78 #endif
79
80 #ifndef internal_function
81 # define internal_function
82 #endif
83
84 /* For those losing systems which don't have `alloca' we have to add
85    some additional code emulating it.  */
86 #ifdef HAVE_ALLOCA
87 # define freea(p) /* nothing */
88 #else
89 # define alloca(n) malloc (n)
90 # define freea(p) free (p)
91 #endif
92
93 #if defined _LIBC_REENTRANT || defined HAVE_FGETS_UNLOCKED
94 # undef fgets
95 # define fgets(buf, len, s) fgets_unlocked (buf, len, s)
96 #endif
97 #if defined _LIBC_REENTRANT || defined HAVE_FEOF_UNLOCKED
98 # undef feof
99 # define feof(s) feof_unlocked (s)
100 #endif
101
102
103 struct alias_map
104 {
105   const char *alias;
106   const char *value;
107 };
108
109
110 static char *string_space;
111 static size_t string_space_act;
112 static size_t string_space_max;
113 static struct alias_map *map;
114 static size_t nmap;
115 static size_t maxmap;
116
117
118 /* Prototypes for local functions.  */
119 static size_t read_alias_file PARAMS ((const char *fname, int fname_len))
120      internal_function;
121 static int extend_alias_table PARAMS ((void));
122 static int alias_compare PARAMS ((const struct alias_map *map1,
123                                   const struct alias_map *map2));
124
125
126 const char *
127 _nl_expand_alias (name)
128     const char *name;
129 {
130   static const char *locale_alias_path = LOCALE_ALIAS_PATH;
131   struct alias_map *retval;
132   const char *result = NULL;
133   size_t added;
134
135 #ifdef _LIBC
136   __libc_lock_lock (lock);
137 #endif
138
139   do
140     {
141       struct alias_map item;
142
143       item.alias = name;
144
145       if (nmap > 0)
146         retval = (struct alias_map *) bsearch (&item, map, nmap,
147                                                sizeof (struct alias_map),
148                                                (int (*) PARAMS ((const void *,
149                                                                  const void *))
150                                                 ) alias_compare);
151       else
152         retval = NULL;
153
154       /* We really found an alias.  Return the value.  */
155       if (retval != NULL)
156         {
157           result = retval->value;
158           break;
159         }
160
161       /* Perhaps we can find another alias file.  */
162       added = 0;
163       while (added == 0 && locale_alias_path[0] != '\0')
164         {
165           const char *start;
166
167           while (locale_alias_path[0] == PATH_SEPARATOR)
168             ++locale_alias_path;
169           start = locale_alias_path;
170
171           while (locale_alias_path[0] != '\0'
172                  && locale_alias_path[0] != PATH_SEPARATOR)
173             ++locale_alias_path;
174
175           if (start < locale_alias_path)
176             added = read_alias_file (start, locale_alias_path - start);
177         }
178     }
179   while (added != 0);
180
181 #ifdef _LIBC
182   __libc_lock_unlock (lock);
183 #endif
184
185   return result;
186 }
187
188
189 static size_t
190 internal_function
191 read_alias_file (fname, fname_len)
192      const char *fname;
193      int fname_len;
194 {
195   FILE *fp;
196   char *full_fname;
197   size_t added;
198   static const char aliasfile[] = "/locale.alias";
199
200   full_fname = (char *) alloca (fname_len + sizeof aliasfile);
201 #ifdef HAVE_MEMPCPY
202   mempcpy (mempcpy (full_fname, fname, fname_len),
203            aliasfile, sizeof aliasfile);
204 #else
205   memcpy (full_fname, fname, fname_len);
206   memcpy (&full_fname[fname_len], aliasfile, sizeof aliasfile);
207 #endif
208
209   fp = fopen (full_fname, "r");
210   freea (full_fname);
211   if (fp == NULL)
212     return 0;
213
214   added = 0;
215   while (!feof (fp))
216     {
217       /* It is a reasonable approach to use a fix buffer here because
218          a) we are only interested in the first two fields
219          b) these fields must be usable as file names and so must not
220             be that long
221        */
222       char buf[BUFSIZ];
223       char *alias;
224       char *value;
225       char *cp;
226
227       if (fgets (buf, sizeof buf, fp) == NULL)
228         /* EOF reached.  */
229         break;
230
231       /* Possibly not the whole line fits into the buffer.  Ignore
232          the rest of the line.  */
233       if (strchr (buf, '\n') == NULL)
234         {
235           char altbuf[BUFSIZ];
236           do
237             if (fgets (altbuf, sizeof altbuf, fp) == NULL)
238               /* Make sure the inner loop will be left.  The outer loop
239                  will exit at the `feof' test.  */
240               break;
241           while (strchr (altbuf, '\n') == NULL);
242         }
243
244       cp = buf;
245       /* Ignore leading white space.  */
246       while (isspace (cp[0]))
247         ++cp;
248
249       /* A leading '#' signals a comment line.  */
250       if (cp[0] != '\0' && cp[0] != '#')
251         {
252           alias = cp++;
253           while (cp[0] != '\0' && !isspace (cp[0]))
254             ++cp;
255           /* Terminate alias name.  */
256           if (cp[0] != '\0')
257             *cp++ = '\0';
258
259           /* Now look for the beginning of the value.  */
260           while (isspace (cp[0]))
261             ++cp;
262
263           if (cp[0] != '\0')
264             {
265               size_t alias_len;
266               size_t value_len;
267
268               value = cp++;
269               while (cp[0] != '\0' && !isspace (cp[0]))
270                 ++cp;
271               /* Terminate value.  */
272               if (cp[0] == '\n')
273                 {
274                   /* This has to be done to make the following test
275                      for the end of line possible.  We are looking for
276                      the terminating '\n' which do not overwrite here.  */
277                   *cp++ = '\0';
278                   *cp = '\n';
279                 }
280               else if (cp[0] != '\0')
281                 *cp++ = '\0';
282
283               if (nmap >= maxmap)
284                 if (__builtin_expect (extend_alias_table (), 0))
285                   return added;
286
287               alias_len = strlen (alias) + 1;
288               value_len = strlen (value) + 1;
289
290               if (string_space_act + alias_len + value_len > string_space_max)
291                 {
292                   /* Increase size of memory pool.  */
293                   size_t new_size = (string_space_max
294                                      + (alias_len + value_len > 1024
295                                         ? alias_len + value_len : 1024));
296                   char *new_pool = (char *) realloc (string_space, new_size);
297                   if (new_pool == NULL)
298                     return added;
299
300                   if (__builtin_expect (string_space != new_pool, 0))
301                     {
302                       size_t i;
303
304                       for (i = 0; i < nmap; i++)
305                         {
306                           map[i].alias += new_pool - string_space;
307                           map[i].value += new_pool - string_space;
308                         }
309                     }
310
311                   string_space = new_pool;
312                   string_space_max = new_size;
313                 }
314
315               map[nmap].alias = memcpy (&string_space[string_space_act],
316                                         alias, alias_len);
317               string_space_act += alias_len;
318
319               map[nmap].value = memcpy (&string_space[string_space_act],
320                                         value, value_len);
321               string_space_act += value_len;
322
323               ++nmap;
324               ++added;
325             }
326         }
327     }
328
329   /* Should we test for ferror()?  I think we have to silently ignore
330      errors.  --drepper  */
331   fclose (fp);
332
333   if (added > 0)
334     qsort (map, nmap, sizeof (struct alias_map),
335            (int (*) PARAMS ((const void *, const void *))) alias_compare);
336
337   return added;
338 }
339
340
341 static int
342 extend_alias_table ()
343 {
344   size_t new_size;
345   struct alias_map *new_map;
346
347   new_size = maxmap == 0 ? 100 : 2 * maxmap;
348   new_map = (struct alias_map *) realloc (map, (new_size
349                                                 * sizeof (struct alias_map)));
350   if (new_map == NULL)
351     /* Simply don't extend: we don't have any more core.  */
352     return -1;
353
354   map = new_map;
355   maxmap = new_size;
356   return 0;
357 }
358
359
360 #ifdef _LIBC
361 static void __attribute__ ((unused))
362 free_mem (void)
363 {
364   if (string_space != NULL)
365     free (string_space);
366   if (map != NULL)
367     free (map);
368 }
369 text_set_element (__libc_subfreeres, free_mem);
370 #endif
371
372
373 static int
374 alias_compare (map1, map2)
375      const struct alias_map *map1;
376      const struct alias_map *map2;
377 {
378 #if defined _LIBC || defined HAVE_STRCASECMP
379   return strcasecmp (map1->alias, map2->alias);
380 #else
381   const unsigned char *p1 = (const unsigned char *) map1->alias;
382   const unsigned char *p2 = (const unsigned char *) map2->alias;
383   unsigned char c1, c2;
384
385   if (p1 == p2)
386     return 0;
387
388   do
389     {
390       /* I know this seems to be odd but the tolower() function in
391          some systems libc cannot handle nonalpha characters.  */
392       c1 = isupper (*p1) ? tolower (*p1) : *p1;
393       c2 = isupper (*p2) ? tolower (*p2) : *p2;
394       if (c1 == '\0')
395         break;
396       ++p1;
397       ++p2;
398     }
399   while (c1 == c2);
400
401   return c1 - c2;
402 #endif
403 }