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