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