1 /* Copyright (C) 1995-2023 Free Software Foundation, Inc.
2 Contributed by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1995.
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 stpcpy().
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 <string.h>
29
30 #if defined _LIBC
31 # include <argz.h>
32 #endif
33 #include <ctype.h>
34 #include <sys/types.h>
35 #include <stdlib.h>
36 #if defined _WIN32 && !defined __CYGWIN__
37 # include <wchar.h>
38 #endif
39
40 #include "loadinfo.h"
41
42 /* On some strange systems still no definition of NULL is found. Sigh! */
43 #ifndef NULL
44 # if defined __STDC__ && __STDC__
45 # define NULL ((void *) 0)
46 # else
47 # define NULL 0
48 # endif
49 #endif
50
51 /* @@ end of prolog @@ */
52
53 #ifdef _LIBC
54 /* Rename the non ANSI C functions. This is required by the standard
55 because some ANSI C functions will require linking with this object
56 file and the name space must not be polluted. */
57 # ifndef stpcpy
58 # define stpcpy(dest, src) __stpcpy(dest, src)
59 # endif
60 #else
61 # ifndef HAVE_STPCPY
62 static char *stpcpy (char *dest, const char *src);
63 # endif
64 #endif
65
66 #ifdef _LIBC
67 # define IS_ABSOLUTE_FILE_NAME(P) ((P)[0] == '/')
68 # define IS_RELATIVE_FILE_NAME(P) (! IS_ABSOLUTE_FILE_NAME (P))
69 #else
70 # include "filename.h"
71 #endif
72
73 /* Return number of bits set in X. */
74 #ifndef ARCH_POP
75 static inline int
76 pop (int x)
77 {
78 /* We assume that no more than 16 bits are used. */
79 x = ((x & ~0x5555) >> 1) + (x & 0x5555);
80 x = ((x & ~0x3333) >> 2) + (x & 0x3333);
81 x = ((x >> 4) + x) & 0x0f0f;
82 x = ((x >> 8) + x) & 0xff;
83
84 return x;
85 }
86 #endif
87
88
89 struct loaded_l10nfile *
90 _nl_make_l10nflist (struct loaded_l10nfile **l10nfile_list,
91 const char *dirlist, size_t dirlist_len,
92 #if defined _WIN32 && !defined __CYGWIN__
93 const wchar_t *wdirlist, size_t wdirlist_len,
94 #endif
95 int mask, const char *language, const char *territory,
96 const char *codeset, const char *normalized_codeset,
97 const char *modifier,
98 const char *filename, int do_allocate)
99 {
100 char *abs_filename;
101 #if defined _WIN32 && !defined __CYGWIN__
102 wchar_t *abs_wfilename;
103 #endif
104 struct loaded_l10nfile **lastp;
105 struct loaded_l10nfile *retval;
106 size_t dirlist_count;
107 size_t entries;
108 int cnt;
109
110 /* If LANGUAGE contains an absolute directory specification, we ignore
111 DIRLIST and WDIRLIST. */
112 if (!IS_RELATIVE_FILE_NAME (language))
113 {
114 dirlist_len = 0;
115 #if defined _WIN32 && !defined __CYGWIN__
116 wdirlist_len = 0;
117 #endif
118 }
119
120 /* Allocate room for the full file name. */
121 abs_filename = (char *) malloc (dirlist_len
122 + strlen (language)
123 + ((mask & XPG_TERRITORY) != 0
124 ? strlen (territory) + 1 : 0)
125 + ((mask & XPG_CODESET) != 0
126 ? strlen (codeset) + 1 : 0)
127 + ((mask & XPG_NORM_CODESET) != 0
128 ? strlen (normalized_codeset) + 1 : 0)
129 + ((mask & XPG_MODIFIER) != 0
130 ? strlen (modifier) + 1 : 0)
131 + 1 + strlen (filename) + 1);
132
133 if (abs_filename == NULL)
134 return NULL;
135
136 /* Construct file name. */
137 {
138 char *cp;
139
140 cp = abs_filename;
141 if (dirlist_len > 0)
142 {
143 memcpy (cp, dirlist, dirlist_len);
144 #ifdef _LIBC
145 __argz_stringify (cp, dirlist_len, PATH_SEPARATOR);
146 #endif
147 cp += dirlist_len;
148 cp[-1] = '/';
149 }
150
151 cp = stpcpy (cp, language);
152
153 if ((mask & XPG_TERRITORY) != 0)
154 {
155 *cp++ = '_';
156 cp = stpcpy (cp, territory);
157 }
158 if ((mask & XPG_CODESET) != 0)
159 {
160 *cp++ = '.';
161 cp = stpcpy (cp, codeset);
162 }
163 if ((mask & XPG_NORM_CODESET) != 0)
164 {
165 *cp++ = '.';
166 cp = stpcpy (cp, normalized_codeset);
167 }
168 if ((mask & XPG_MODIFIER) != 0)
169 {
170 *cp++ = '@';
171 cp = stpcpy (cp, modifier);
172 }
173
174 *cp++ = '/';
175 stpcpy (cp, filename);
176 }
177
178 #if defined _WIN32 && !defined __CYGWIN__
179 /* Construct wide-char file name. */
180 if (wdirlist_len > 0)
181 {
182 /* Since dirlist_len == 0, just concatenate wdirlist and abs_filename. */
183 /* An upper bound for wcslen (mbstowcs (abs_filename)). */
184 size_t abs_filename_bound = mbstowcs (NULL, abs_filename, 0);
185 if (abs_filename_bound == (size_t)-1)
186 {
187 free (abs_filename);
188 return NULL;
189 }
190
191 /* Allocate and fill abs_wfilename. */
192 abs_wfilename =
193 (wchar_t *)
194 malloc ((wdirlist_len + abs_filename_bound + 1) * sizeof (wchar_t));
195 if (abs_wfilename == NULL)
196 {
197 free (abs_filename);
198 return NULL;
199 }
200 wmemcpy (abs_wfilename, wdirlist, wdirlist_len - 1);
201 abs_wfilename[wdirlist_len - 1] = L'/';
202 if (mbstowcs (abs_wfilename + wdirlist_len, abs_filename,
203 abs_filename_bound + 1)
204 > abs_filename_bound)
205 {
206 free (abs_filename);
207 free (abs_wfilename);
208 return NULL;
209 }
210
211 free (abs_filename);
212 abs_filename = NULL;
213 }
214 else
215 abs_wfilename = NULL;
216 #endif
217
218 /* Look in list of already loaded domains whether it is already
219 available. */
220 lastp = l10nfile_list;
221 #if defined _WIN32 && !defined __CYGWIN__
222 if (abs_wfilename != NULL)
223 {
224 for (retval = *l10nfile_list; retval != NULL; retval = retval->next)
225 {
226 if (retval->wfilename != NULL)
227 {
228 int compare = wcscmp (retval->wfilename, abs_wfilename);
229 if (compare == 0)
230 /* We found it! */
231 break;
232 if (compare < 0)
233 {
234 /* It's not in the list, and we have found the place where it
235 needs to be inserted: at *LASTP. */
236 retval = NULL;
237 break;
238 }
239 }
240 lastp = &retval->next;
241 }
242 }
243 else
244 #endif
245 for (retval = *l10nfile_list; retval != NULL; retval = retval->next)
246 {
247 #if defined _WIN32 && !defined __CYGWIN__
248 if (retval->filename != NULL)
249 #endif
250 {
251 int compare = strcmp (retval->filename, abs_filename);
252 if (compare == 0)
253 /* We found it! */
254 break;
255 if (compare < 0)
256 {
257 /* It's not in the list, and we have found the place where it
258 needs to be inserted: at *LASTP. */
259 retval = NULL;
260 break;
261 }
262 }
263 lastp = &retval->next;
264 }
265
266 if (retval != NULL || do_allocate == 0)
267 {
268 free (abs_filename);
269 #if defined _WIN32 && !defined __CYGWIN__
270 free (abs_wfilename);
271 #endif
272 return retval;
273 }
274
275 #ifdef _LIBC
276 dirlist_count = (dirlist_len > 0 ? __argz_count (dirlist, dirlist_len) : 1);
277 #else
278 dirlist_count = 1;
279 #endif
280
281 /* Allocate a new loaded_l10nfile. */
282 retval =
283 (struct loaded_l10nfile *)
284 malloc (sizeof (*retval)
285 + (((dirlist_count << pop (mask)) + (dirlist_count > 1 ? 1 : 0))
286 * sizeof (struct loaded_l10nfile *)));
287 if (retval == NULL)
288 {
289 free (abs_filename);
290 #if defined _WIN32 && !defined __CYGWIN__
291 free (abs_wfilename);
292 #endif
293 return NULL;
294 }
295
296 retval->filename = abs_filename;
297 #if defined _WIN32 && !defined __CYGWIN__
298 retval->wfilename = abs_wfilename;
299 #endif
300
301 /* We set retval->data to NULL here; it is filled in later.
302 Setting retval->decided to 1 here means that retval does not
303 correspond to a real file (dirlist_count > 1) or is not worth
304 looking up (if an unnormalized codeset was specified). */
305 retval->decided = (dirlist_count > 1
306 || ((mask & XPG_CODESET) != 0
307 && (mask & XPG_NORM_CODESET) != 0));
308 retval->data = NULL;
309
310 retval->next = *lastp;
311 *lastp = retval;
312
313 entries = 0;
314 /* Recurse to fill the inheritance list of RETVAL.
315 If the DIRLIST is a real list (i.e. DIRLIST_COUNT > 1), the RETVAL
316 entry does not correspond to a real file; retval->filename contains
317 colons. In this case we loop across all elements of DIRLIST and
318 across all bit patterns dominated by MASK.
319 If the DIRLIST is a single directory or entirely redundant (i.e.
320 DIRLIST_COUNT == 1), we loop across all bit patterns dominated by
321 MASK, excluding MASK itself.
322 In either case, we loop down from MASK to 0. This has the effect
323 that the extra bits in the locale name are dropped in this order:
324 first the modifier, then the territory, then the codeset, then the
325 normalized_codeset. */
326 for (cnt = dirlist_count > 1 ? mask : mask - 1; cnt >= 0; --cnt)
327 if ((cnt & ~mask) == 0
328 && !((cnt & XPG_CODESET) != 0 && (cnt & XPG_NORM_CODESET) != 0))
329 {
330 #ifdef _LIBC
331 if (dirlist_count > 1)
332 {
333 /* Iterate over all elements of the DIRLIST. */
334 char *dir = NULL;
335
336 while ((dir = __argz_next ((char *) dirlist, dirlist_len, dir))
337 != NULL)
338 retval->successor[entries++]
339 = _nl_make_l10nflist (l10nfile_list, dir, strlen (dir) + 1,
340 cnt, language, territory, codeset,
341 normalized_codeset, modifier, filename,
342 1);
343 }
344 else
345 #endif
346 retval->successor[entries++]
347 = _nl_make_l10nflist (l10nfile_list,
348 dirlist, dirlist_len,
349 #if defined _WIN32 && !defined __CYGWIN__
350 wdirlist, wdirlist_len,
351 #endif
352 cnt, language, territory, codeset,
353 normalized_codeset, modifier, filename, 1);
354 }
355 retval->successor[entries] = NULL;
356
357 return retval;
358 }
359
360 /* Normalize codeset name. There is no standard for the codeset
361 names. Normalization allows the user to use any of the common
362 names. The return value is dynamically allocated and has to be
363 freed by the caller. */
364 const char *
365 _nl_normalize_codeset (const char *codeset, size_t name_len)
366 {
367 size_t len = 0;
368 int only_digit = 1;
369 char *retval;
370 char *wp;
371 size_t cnt;
372
373 for (cnt = 0; cnt < name_len; ++cnt)
374 if (isalnum ((unsigned char) codeset[cnt]))
375 {
376 ++len;
377
378 if (isalpha ((unsigned char) codeset[cnt]))
379 only_digit = 0;
380 }
381
382 retval = (char *) malloc ((only_digit ? 3 : 0) + len + 1);
383
384 if (retval != NULL)
385 {
386 if (only_digit)
387 wp = stpcpy (retval, "iso");
388 else
389 wp = retval;
390
391 for (cnt = 0; cnt < name_len; ++cnt)
392 if (isalpha ((unsigned char) codeset[cnt]))
393 *wp++ = tolower ((unsigned char) codeset[cnt]);
394 else if (isdigit ((unsigned char) codeset[cnt]))
395 *wp++ = codeset[cnt];
396
397 *wp = '\0';
398 }
399
400 return (const char *) retval;
401 }
402
403
404 /* @@ begin of epilog @@ */
405
406 /* We don't want libintl.a to depend on any other library. So we
407 avoid the non-standard function stpcpy. In GNU C Library this
408 function is available, though. Also allow the symbol HAVE_STPCPY
409 to be defined. */
410 #if !_LIBC && !HAVE_STPCPY
411 static char *
412 stpcpy (char *dest, const char *src)
413 {
414 while ((*dest++ = *src++) != '\0')
415 /* Do nothing. */ ;
416 return dest - 1;
417 }
418 #endif