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