1/* Functions to read locale data files.
2 Copyright (C) 1996-2024 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 The GNU C Library 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 GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; if not, see
17 <https://www.gnu.org/licenses/>. */
18
19#include <assert.h>
20#include <errno.h>
21#include <fcntl.h>
22#include <locale.h>
23#include <stdlib.h>
24#include <string.h>
25#include <unistd.h>
26#ifdef _POSIX_MAPPED_FILES
27# include <sys/mman.h>
28#endif
29#include <sys/stat.h>
30
31#include <not-cancel.h>
32#include "localeinfo.h"
33
34
35static const size_t _nl_category_num_items[] =
36{
37#define DEFINE_CATEGORY(category, category_name, items, a) \
38 [category] = _NL_ITEM_INDEX (_NL_NUM_##category),
39#include "categories.def"
40#undef DEFINE_CATEGORY
41};
42
43
44#define NO_PAREN(arg, rest...) arg, ##rest
45
46/* The size of the array must be specified explicitly because some of
47 the 'items' may be subarrays, which will cause the compiler to deduce
48 an incorrect size from the initializer. */
49#define DEFINE_CATEGORY(category, category_name, items, a) \
50static const enum value_type _nl_value_type_##category \
51 [_NL_ITEM_INDEX (_NL_NUM_##category)] = { NO_PAREN items };
52#define DEFINE_ELEMENT(element, element_name, optstd, type, rest...) \
53 [_NL_ITEM_INDEX (element)] = type,
54#include "categories.def"
55#undef DEFINE_CATEGORY
56
57static const enum value_type *const _nl_value_types[] =
58{
59#define DEFINE_CATEGORY(category, category_name, items, a) \
60 [category] = _nl_value_type_##category,
61#include "categories.def"
62#undef DEFINE_CATEGORY
63};
64
65/* Fill in LOCDATA->private for the LC_CTYPE category. */
66static void
67_nl_intern_locale_data_fill_cache_ctype (struct __locale_data *locdata)
68{
69 struct lc_ctype_data *data = locdata->private;
70
71 /* Default to no translation. Assumes zero initialization of *data. */
72 memset (data->outdigit_bytes, 1, sizeof (data->outdigit_bytes));
73
74 for (int i = 0; i <= 9; ++i)
75 {
76 const char *digit
77 = locdata->values[_NL_ITEM_INDEX (_NL_CTYPE_OUTDIGIT0_MB + i)].string;
78 unsigned char len;
79 if (digit[0] != '0' + i || digit[1] != '\0')
80 {
81 data->outdigit_translation_needed = true;
82 len = strlen (locdata->values[_NL_ITEM_INDEX
83 (_NL_CTYPE_OUTDIGIT0_MB + i)].string);
84 }
85 else
86 len = 1;
87 data->outdigit_bytes[i] = len;
88 if (i == 0)
89 data->outdigit_bytes_all_equal = len;
90 else if (data->outdigit_bytes_all_equal != len)
91 data->outdigit_bytes_all_equal = 0;
92 }
93}
94
95/* Updates data in LOCDATA->private for CATEGORY. */
96static void
97_nl_intern_locale_data_fill_cache (int category, struct __locale_data *locdata)
98{
99 switch (category)
100 {
101 case LC_CTYPE:
102 _nl_intern_locale_data_fill_cache_ctype (locdata);
103 break;
104 }
105}
106
107/* Returns the number of bytes allocated of struct __locale_data for
108 CATEGORY. */
109static size_t
110_nl_intern_locale_data_extra_size (int category)
111{
112 switch (category)
113 {
114 case LC_CTYPE:
115 return sizeof (struct lc_ctype_data);
116 default:
117 return 0;
118 }
119}
120
121struct __locale_data *
122_nl_intern_locale_data (int category, const void *data, size_t datasize)
123{
124 const struct
125 {
126 unsigned int magic;
127 unsigned int nstrings;
128 unsigned int strindex[0];
129 } *const filedata = data;
130 struct __locale_data *newdata;
131 size_t cnt;
132
133 if (__builtin_expect (datasize < sizeof *filedata, 0)
134 || __builtin_expect (filedata->magic != LIMAGIC (category), 0))
135 {
136 /* Bad data file. */
137 __set_errno (EINVAL);
138 return NULL;
139 }
140
141 if (__builtin_expect (filedata->nstrings < _nl_category_num_items[category],
142 0)
143 || (__builtin_expect (sizeof *filedata
144 + filedata->nstrings * sizeof (unsigned int)
145 >= datasize, 0)))
146 {
147 /* Insufficient data. */
148 __set_errno (EINVAL);
149 return NULL;
150 }
151
152 size_t base_size = (sizeof *newdata
153 + filedata->nstrings * sizeof (union locale_data_value));
154 size_t extra_size = _nl_intern_locale_data_extra_size (category);
155
156 newdata = malloc (size: base_size + extra_size);
157 if (newdata == NULL)
158 return NULL;
159
160 newdata->filedata = (void *) filedata;
161 newdata->filesize = datasize;
162 if (extra_size == 0)
163 newdata->private = NULL;
164 else
165 {
166 newdata->private = (char *) newdata + base_size;
167 memset (newdata->private, 0, extra_size);
168 }
169 newdata->usage_count = 0;
170 newdata->use_translit = 0;
171 newdata->nstrings = filedata->nstrings;
172 for (cnt = 0; cnt < newdata->nstrings; ++cnt)
173 {
174 size_t idx = filedata->strindex[cnt];
175 if (__glibc_unlikely (idx > (size_t) newdata->filesize))
176 {
177 puntdata:
178 free (ptr: newdata);
179 __set_errno (EINVAL);
180 return NULL;
181 }
182
183 /* Determine the type. There is one special case: the LC_CTYPE
184 category can have more elements than there are in the
185 _nl_value_type_LC_XYZ array. There are all pointers. */
186 switch (category)
187 {
188#define CATTEST(cat) \
189 case LC_##cat: \
190 if (cnt >= (sizeof (_nl_value_type_LC_##cat) \
191 / sizeof (_nl_value_type_LC_##cat[0]))) \
192 goto puntdata; \
193 break
194 CATTEST (NUMERIC);
195 CATTEST (TIME);
196 CATTEST (COLLATE);
197 CATTEST (MONETARY);
198 CATTEST (MESSAGES);
199 CATTEST (PAPER);
200 CATTEST (NAME);
201 CATTEST (ADDRESS);
202 CATTEST (TELEPHONE);
203 CATTEST (MEASUREMENT);
204 CATTEST (IDENTIFICATION);
205 default:
206 assert (category == LC_CTYPE);
207 break;
208 }
209
210 if ((category == LC_CTYPE
211 && cnt >= (sizeof (_nl_value_type_LC_CTYPE)
212 / sizeof (_nl_value_type_LC_CTYPE[0])))
213 || __builtin_expect (_nl_value_types[category][cnt] != word, 1))
214 newdata->values[cnt].string = newdata->filedata + idx;
215 else
216 {
217 if (!LOCFILE_ALIGNED_P (idx))
218 goto puntdata;
219 newdata->values[cnt].word =
220 *((const uint32_t *) (newdata->filedata + idx));
221 }
222 }
223
224 if (extra_size > 0)
225 _nl_intern_locale_data_fill_cache (category, locdata: newdata);
226
227 return newdata;
228}
229
230void
231_nl_load_locale (struct loaded_l10nfile *file, int category)
232{
233 int fd;
234 void *filedata;
235 struct __stat64_t64 st;
236 struct __locale_data *newdata;
237 int save_err;
238 int alloc = ld_mapped;
239
240 file->decided = 1;
241 file->data = NULL;
242
243 fd = __open_nocancel (file->filename, O_RDONLY | O_CLOEXEC);
244 if (__builtin_expect (fd, 0) < 0)
245 /* Cannot open the file. */
246 return;
247
248 if (__glibc_unlikely (__fstat64_time64 (fd, &st) < 0))
249 {
250 puntfd:
251 __close_nocancel_nostatus (fd);
252 return;
253 }
254 if (__glibc_unlikely (S_ISDIR (st.st_mode)))
255 {
256 /* LOCALE/LC_foo is a directory; open LOCALE/LC_foo/SYS_LC_foo
257 instead. */
258 char *newp;
259 size_t filenamelen;
260
261 __close_nocancel_nostatus (fd);
262
263 filenamelen = strlen (file->filename);
264 newp = (char *) alloca (filenamelen
265 + 5 + _nl_category_name_sizes[category] + 1);
266 __mempcpy (__mempcpy (__mempcpy (newp, file->filename, filenamelen),
267 "/SYS_", 5), _nl_category_names_get (category),
268 _nl_category_name_sizes[category] + 1);
269
270 fd = __open_nocancel (newp, O_RDONLY | O_CLOEXEC);
271 if (__builtin_expect (fd, 0) < 0)
272 return;
273
274 if (__glibc_unlikely (__fstat64_time64 (fd, &st) < 0))
275 goto puntfd;
276 }
277
278 /* Map in the file's data. */
279 save_err = errno;
280#ifdef _POSIX_MAPPED_FILES
281# ifndef MAP_COPY
282 /* Linux seems to lack read-only copy-on-write. */
283# define MAP_COPY MAP_PRIVATE
284# endif
285# ifndef MAP_FILE
286 /* Some systems do not have this flag; it is superfluous. */
287# define MAP_FILE 0
288# endif
289 filedata = __mmap ((caddr_t) 0, st.st_size,
290 PROT_READ, MAP_FILE|MAP_COPY, fd, 0);
291 if (__glibc_unlikely (filedata == MAP_FAILED))
292 {
293 filedata = NULL;
294 if (__builtin_expect (errno, ENOSYS) == ENOSYS)
295 {
296#endif /* _POSIX_MAPPED_FILES */
297 /* No mmap; allocate a buffer and read from the file. */
298 alloc = ld_malloced;
299 filedata = malloc (size: st.st_size);
300 if (filedata != NULL)
301 {
302 off_t to_read = st.st_size;
303 ssize_t nread;
304 char *p = (char *) filedata;
305 while (to_read > 0)
306 {
307 nread = __read_nocancel (fd, p, to_read);
308 if (__builtin_expect (nread, 1) <= 0)
309 {
310 free (ptr: filedata);
311 if (nread == 0)
312 __set_errno (EINVAL); /* Bizarreness going on. */
313 goto puntfd;
314 }
315 p += nread;
316 to_read -= nread;
317 }
318 __set_errno (save_err);
319 }
320#ifdef _POSIX_MAPPED_FILES
321 }
322 }
323#endif /* _POSIX_MAPPED_FILES */
324
325 /* We have mapped the data, so we no longer need the descriptor. */
326 __close_nocancel_nostatus (fd);
327
328 if (__glibc_unlikely (filedata == NULL))
329 /* We failed to map or read the data. */
330 return;
331
332 newdata = _nl_intern_locale_data (category, data: filedata, datasize: st.st_size);
333 if (__glibc_unlikely (newdata == NULL))
334 /* Bad data. */
335 {
336#ifdef _POSIX_MAPPED_FILES
337 if (alloc == ld_mapped)
338 __munmap ((caddr_t) filedata, st.st_size);
339#endif
340 return;
341 }
342
343 /* _nl_intern_locale_data leaves us these fields to initialize. */
344 newdata->name = NULL; /* This will be filled if necessary in findlocale.c. */
345 newdata->alloc = alloc;
346
347 file->data = newdata;
348}
349
350void
351_nl_unload_locale (int category, struct __locale_data *locale)
352{
353 /* Deallocate locale->private. */
354 switch (category)
355 {
356 case LC_CTYPE:
357 _nl_cleanup_ctype (locale);
358 break;
359 case LC_TIME:
360 _nl_cleanup_time (locale);
361 break;
362 }
363
364 switch (__builtin_expect (locale->alloc, ld_mapped))
365 {
366 case ld_malloced:
367 free (ptr: (void *) locale->filedata);
368 break;
369 case ld_mapped:
370#ifdef _POSIX_MAPPED_FILES
371 __munmap ((caddr_t) locale->filedata, locale->filesize);
372 break;
373#endif
374 case ld_archive: /* Nothing to do. */
375 break;
376 }
377
378 if (__builtin_expect (locale->alloc, ld_mapped) != ld_archive)
379 free (ptr: (char *) locale->name);
380
381 free (ptr: locale);
382}
383

source code of glibc/locale/loadlocale.c