]>
Commit | Line | Data |
---|---|---|
04277e02 | 1 | /* Copyright (C) 1996-2019 Free Software Foundation, Inc. |
6d52618b | 2 | This file is part of the GNU C Library. |
e5681dee | 3 | Contributed by Ulrich Drepper <drepper@gnu.org>, 1996. |
19bc17a9 | 4 | |
43bc8ac6 | 5 | This program is free software; you can redistribute it and/or modify |
2e2efe65 RM |
6 | it under the terms of the GNU General Public License as published |
7 | by the Free Software Foundation; version 2 of the License, or | |
8 | (at your option) any later version. | |
19bc17a9 | 9 | |
43bc8ac6 | 10 | This program is distributed in the hope that it will be useful, |
6d52618b | 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
43bc8ac6 UD |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | GNU General Public License for more details. | |
19bc17a9 | 14 | |
43bc8ac6 | 15 | You should have received a copy of the GNU General Public License |
5a82c748 | 16 | along with this program; if not, see <https://www.gnu.org/licenses/>. */ |
19bc17a9 RM |
17 | |
18 | #ifdef HAVE_CONFIG_H | |
19 | # include <config.h> | |
20 | #endif | |
21 | ||
aac0e8c4 | 22 | #include <dirent.h> |
19bc17a9 RM |
23 | #include <errno.h> |
24 | #include <fcntl.h> | |
6055173a | 25 | #include <stdbool.h> |
f166d865 | 26 | #include <stdlib.h> |
aac0e8c4 | 27 | #include <string.h> |
19bc17a9 | 28 | #include <unistd.h> |
4b10dd6c | 29 | #include <sys/param.h> |
19bc17a9 | 30 | #include <sys/stat.h> |
1ecbb381 RS |
31 | #include <assert.h> |
32 | #include <wchar.h> | |
19bc17a9 | 33 | |
a7b65cdc | 34 | #include "../../crypt/md5.h" |
4b10dd6c | 35 | #include "localedef.h" |
1ecbb381 | 36 | #include "localeinfo.h" |
19bc17a9 | 37 | #include "locfile.h" |
a7b65cdc | 38 | #include "simple-hash.h" |
19bc17a9 | 39 | |
19bc17a9 RM |
40 | #include "locfile-kw.h" |
41 | ||
1ecbb381 RS |
42 | #define obstack_chunk_alloc xmalloc |
43 | #define obstack_chunk_free free | |
19bc17a9 | 44 | |
a7b65cdc UD |
45 | /* Temporary storage of the locale data before writing it to the archive. */ |
46 | static locale_data_t to_archive; | |
47 | ||
48 | ||
4b10dd6c | 49 | int |
47e8b443 | 50 | locfile_read (struct localedef_t *result, const struct charmap_t *charmap) |
19bc17a9 | 51 | { |
4b10dd6c UD |
52 | const char *filename = result->name; |
53 | const char *repertoire_name = result->repertoire_name; | |
69f6a804 | 54 | int locale_mask = result->needed & ~result->avail; |
19bc17a9 | 55 | struct linereader *ldfile; |
b9eb05d6 | 56 | int not_here = ALL_LOCALES; |
19bc17a9 | 57 | |
4b10dd6c UD |
58 | /* If no repertoire name was specified use the global one. */ |
59 | if (repertoire_name == NULL) | |
60 | repertoire_name = repertoire_global; | |
19bc17a9 | 61 | |
4b10dd6c | 62 | /* Open the locale definition file. */ |
19bc17a9 RM |
63 | ldfile = lr_open (filename, locfile_hash); |
64 | if (ldfile == NULL) | |
65 | { | |
dcf56f42 | 66 | if (filename != NULL && filename[0] != '/') |
19bc17a9 | 67 | { |
4b10dd6c | 68 | char *i18npath = getenv ("I18NPATH"); |
f166d865 UD |
69 | if (i18npath != NULL && *i18npath != '\0') |
70 | { | |
db2f05ba RM |
71 | const size_t pathlen = strlen (i18npath); |
72 | char i18npathbuf[pathlen + 1]; | |
73 | char path[strlen (filename) + 1 + pathlen | |
74 | + sizeof ("/locales/") - 1]; | |
f166d865 | 75 | char *next; |
db2f05ba | 76 | i18npath = memcpy (i18npathbuf, i18npath, pathlen + 1); |
f166d865 UD |
77 | |
78 | while (ldfile == NULL | |
79 | && (next = strsep (&i18npath, ":")) != NULL) | |
80 | { | |
12a9fabe | 81 | stpcpy (stpcpy (stpcpy (path, next), "/locales/"), filename); |
f166d865 UD |
82 | |
83 | ldfile = lr_open (path, locfile_hash); | |
4b10dd6c UD |
84 | |
85 | if (ldfile == NULL) | |
86 | { | |
b60ea6ff | 87 | stpcpy (stpcpy (stpcpy (path, next), "/"), filename); |
4b10dd6c UD |
88 | |
89 | ldfile = lr_open (path, locfile_hash); | |
90 | } | |
f166d865 UD |
91 | } |
92 | } | |
93 | ||
94 | /* Test in the default directory. */ | |
95 | if (ldfile == NULL) | |
96 | { | |
97 | char path[strlen (filename) + 1 + sizeof (LOCSRCDIR)]; | |
98 | ||
99 | stpcpy (stpcpy (stpcpy (path, LOCSRCDIR), "/"), filename); | |
100 | ldfile = lr_open (path, locfile_hash); | |
101 | } | |
19bc17a9 RM |
102 | } |
103 | ||
104 | if (ldfile == NULL) | |
4b10dd6c | 105 | return 1; |
19bc17a9 RM |
106 | } |
107 | ||
4b10dd6c | 108 | /* Parse locale definition file and store result in RESULT. */ |
19bc17a9 RM |
109 | while (1) |
110 | { | |
47e8b443 | 111 | struct token *now = lr_token (ldfile, charmap, NULL, NULL, verbose); |
19bc17a9 RM |
112 | enum token_t nowtok = now->tok; |
113 | struct token *arg; | |
114 | ||
115 | if (nowtok == tok_eof) | |
116 | break; | |
117 | ||
4b10dd6c UD |
118 | if (nowtok == tok_eol) |
119 | /* Ignore empty lines. */ | |
120 | continue; | |
19bc17a9 | 121 | |
4b10dd6c UD |
122 | switch (nowtok) |
123 | { | |
124 | case tok_escape_char: | |
125 | case tok_comment_char: | |
126 | /* We need an argument. */ | |
47e8b443 | 127 | arg = lr_token (ldfile, charmap, NULL, NULL, verbose); |
19bc17a9 | 128 | |
4b10dd6c | 129 | if (arg->tok != tok_ident) |
19bc17a9 | 130 | { |
4b10dd6c | 131 | SYNTAX_ERROR (_("bad argument")); |
19bc17a9 RM |
132 | continue; |
133 | } | |
134 | ||
4b10dd6c | 135 | if (arg->val.str.lenmb != 1) |
19bc17a9 | 136 | { |
4b10dd6c UD |
137 | lr_error (ldfile, _("\ |
138 | argument to `%s' must be a single character"), | |
139 | nowtok == tok_escape_char | |
140 | ? "escape_char" : "comment_char"); | |
19bc17a9 | 141 | |
4b10dd6c | 142 | lr_ignore_rest (ldfile, 0); |
19bc17a9 RM |
143 | continue; |
144 | } | |
145 | ||
4b10dd6c UD |
146 | if (nowtok == tok_escape_char) |
147 | ldfile->escape_char = *arg->val.str.startmb; | |
148 | else | |
149 | ldfile->comment_char = *arg->val.str.startmb; | |
150 | break; | |
19bc17a9 | 151 | |
4b10dd6c UD |
152 | case tok_repertoiremap: |
153 | /* We need an argument. */ | |
47e8b443 | 154 | arg = lr_token (ldfile, charmap, NULL, NULL, verbose); |
19bc17a9 | 155 | |
4b10dd6c | 156 | if (arg->tok != tok_ident) |
19bc17a9 | 157 | { |
4b10dd6c | 158 | SYNTAX_ERROR (_("bad argument")); |
19bc17a9 RM |
159 | continue; |
160 | } | |
161 | ||
4b10dd6c | 162 | if (repertoire_name == NULL) |
19bc17a9 | 163 | { |
51e59260 UD |
164 | char *newp = alloca (arg->val.str.lenmb + 1); |
165 | ||
166 | *((char *) mempcpy (newp, arg->val.str.startmb, | |
167 | arg->val.str.lenmb)) = '\0'; | |
168 | repertoire_name = newp; | |
19bc17a9 | 169 | } |
4b10dd6c | 170 | break; |
19bc17a9 | 171 | |
4b10dd6c UD |
172 | case tok_lc_ctype: |
173 | ctype_read (ldfile, result, charmap, repertoire_name, | |
174 | (locale_mask & CTYPE_LOCALE) == 0); | |
175 | result->avail |= locale_mask & CTYPE_LOCALE; | |
b9eb05d6 | 176 | not_here ^= CTYPE_LOCALE; |
19bc17a9 RM |
177 | continue; |
178 | ||
4b10dd6c UD |
179 | case tok_lc_collate: |
180 | collate_read (ldfile, result, charmap, repertoire_name, | |
181 | (locale_mask & COLLATE_LOCALE) == 0); | |
182 | result->avail |= locale_mask & COLLATE_LOCALE; | |
b9eb05d6 | 183 | not_here ^= COLLATE_LOCALE; |
19bc17a9 RM |
184 | continue; |
185 | ||
4b10dd6c UD |
186 | case tok_lc_monetary: |
187 | monetary_read (ldfile, result, charmap, repertoire_name, | |
188 | (locale_mask & MONETARY_LOCALE) == 0); | |
189 | result->avail |= locale_mask & MONETARY_LOCALE; | |
b9eb05d6 | 190 | not_here ^= MONETARY_LOCALE; |
19bc17a9 RM |
191 | continue; |
192 | ||
4b10dd6c UD |
193 | case tok_lc_numeric: |
194 | numeric_read (ldfile, result, charmap, repertoire_name, | |
195 | (locale_mask & NUMERIC_LOCALE) == 0); | |
196 | result->avail |= locale_mask & NUMERIC_LOCALE; | |
b9eb05d6 | 197 | not_here ^= NUMERIC_LOCALE; |
19bc17a9 RM |
198 | continue; |
199 | ||
4b10dd6c UD |
200 | case tok_lc_time: |
201 | time_read (ldfile, result, charmap, repertoire_name, | |
202 | (locale_mask & TIME_LOCALE) == 0); | |
203 | result->avail |= locale_mask & TIME_LOCALE; | |
b9eb05d6 | 204 | not_here ^= TIME_LOCALE; |
19bc17a9 RM |
205 | continue; |
206 | ||
4b10dd6c UD |
207 | case tok_lc_messages: |
208 | messages_read (ldfile, result, charmap, repertoire_name, | |
209 | (locale_mask & MESSAGES_LOCALE) == 0); | |
210 | result->avail |= locale_mask & MESSAGES_LOCALE; | |
b9eb05d6 | 211 | not_here ^= MESSAGES_LOCALE; |
19bc17a9 RM |
212 | continue; |
213 | ||
4b10dd6c UD |
214 | case tok_lc_paper: |
215 | paper_read (ldfile, result, charmap, repertoire_name, | |
216 | (locale_mask & PAPER_LOCALE) == 0); | |
217 | result->avail |= locale_mask & PAPER_LOCALE; | |
b9eb05d6 | 218 | not_here ^= PAPER_LOCALE; |
19bc17a9 RM |
219 | continue; |
220 | ||
4b10dd6c UD |
221 | case tok_lc_name: |
222 | name_read (ldfile, result, charmap, repertoire_name, | |
223 | (locale_mask & NAME_LOCALE) == 0); | |
224 | result->avail |= locale_mask & NAME_LOCALE; | |
b9eb05d6 | 225 | not_here ^= NAME_LOCALE; |
19bc17a9 RM |
226 | continue; |
227 | ||
4b10dd6c UD |
228 | case tok_lc_address: |
229 | address_read (ldfile, result, charmap, repertoire_name, | |
230 | (locale_mask & ADDRESS_LOCALE) == 0); | |
231 | result->avail |= locale_mask & ADDRESS_LOCALE; | |
b9eb05d6 | 232 | not_here ^= ADDRESS_LOCALE; |
19bc17a9 RM |
233 | continue; |
234 | ||
4b10dd6c UD |
235 | case tok_lc_telephone: |
236 | telephone_read (ldfile, result, charmap, repertoire_name, | |
237 | (locale_mask & TELEPHONE_LOCALE) == 0); | |
238 | result->avail |= locale_mask & TELEPHONE_LOCALE; | |
b9eb05d6 | 239 | not_here ^= TELEPHONE_LOCALE; |
19bc17a9 RM |
240 | continue; |
241 | ||
4b10dd6c UD |
242 | case tok_lc_measurement: |
243 | measurement_read (ldfile, result, charmap, repertoire_name, | |
244 | (locale_mask & MEASUREMENT_LOCALE) == 0); | |
245 | result->avail |= locale_mask & MEASUREMENT_LOCALE; | |
b9eb05d6 | 246 | not_here ^= MEASUREMENT_LOCALE; |
19bc17a9 RM |
247 | continue; |
248 | ||
4b10dd6c UD |
249 | case tok_lc_identification: |
250 | identification_read (ldfile, result, charmap, repertoire_name, | |
251 | (locale_mask & IDENTIFICATION_LOCALE) == 0); | |
252 | result->avail |= locale_mask & IDENTIFICATION_LOCALE; | |
b9eb05d6 | 253 | not_here ^= IDENTIFICATION_LOCALE; |
19bc17a9 RM |
254 | continue; |
255 | ||
256 | default: | |
4b10dd6c UD |
257 | SYNTAX_ERROR (_("\ |
258 | syntax error: not inside a locale definition section")); | |
259 | continue; | |
19bc17a9 RM |
260 | } |
261 | ||
4b10dd6c UD |
262 | /* The rest of the line must be empty. */ |
263 | lr_ignore_rest (ldfile, 1); | |
19bc17a9 RM |
264 | } |
265 | ||
266 | /* We read all of the file. */ | |
267 | lr_close (ldfile); | |
268 | ||
b9eb05d6 UD |
269 | /* Mark the categories which are not contained in the file. We assume |
270 | them to be available and the default data will be used. */ | |
271 | result->avail |= not_here; | |
272 | ||
4b10dd6c | 273 | return 0; |
19bc17a9 RM |
274 | } |
275 | ||
276 | ||
aac0e8c4 UD |
277 | /* Semantic checking of locale specifications. */ |
278 | ||
4b10dd6c | 279 | static void (*const check_funcs[]) (struct localedef_t *, |
47e8b443 | 280 | const struct charmap_t *) = |
4b10dd6c UD |
281 | { |
282 | [LC_CTYPE] = ctype_finish, | |
283 | [LC_COLLATE] = collate_finish, | |
284 | [LC_MESSAGES] = messages_finish, | |
285 | [LC_MONETARY] = monetary_finish, | |
286 | [LC_NUMERIC] = numeric_finish, | |
287 | [LC_TIME] = time_finish, | |
288 | [LC_PAPER] = paper_finish, | |
289 | [LC_NAME] = name_finish, | |
290 | [LC_ADDRESS] = address_finish, | |
291 | [LC_TELEPHONE] = telephone_finish, | |
292 | [LC_MEASUREMENT] = measurement_finish, | |
293 | [LC_IDENTIFICATION] = identification_finish | |
294 | }; | |
295 | ||
19bc17a9 | 296 | void |
4b10dd6c | 297 | check_all_categories (struct localedef_t *definitions, |
47e8b443 | 298 | const struct charmap_t *charmap) |
19bc17a9 | 299 | { |
4b10dd6c UD |
300 | int cnt; |
301 | ||
302 | for (cnt = 0; cnt < sizeof (check_funcs) / sizeof (check_funcs[0]); ++cnt) | |
303 | if (check_funcs[cnt] != NULL) | |
304 | check_funcs[cnt] (definitions, charmap); | |
19bc17a9 RM |
305 | } |
306 | ||
307 | ||
aac0e8c4 UD |
308 | /* Writing the locale data files. All files use the same output_path. */ |
309 | ||
47e8b443 UD |
310 | static void (*const write_funcs[]) (struct localedef_t *, |
311 | const struct charmap_t *, const char *) = | |
4b10dd6c UD |
312 | { |
313 | [LC_CTYPE] = ctype_output, | |
314 | [LC_COLLATE] = collate_output, | |
315 | [LC_MESSAGES] = messages_output, | |
316 | [LC_MONETARY] = monetary_output, | |
317 | [LC_NUMERIC] = numeric_output, | |
318 | [LC_TIME] = time_output, | |
319 | [LC_PAPER] = paper_output, | |
320 | [LC_NAME] = name_output, | |
321 | [LC_ADDRESS] = address_output, | |
322 | [LC_TELEPHONE] = telephone_output, | |
323 | [LC_MEASUREMENT] = measurement_output, | |
324 | [LC_IDENTIFICATION] = identification_output | |
325 | }; | |
326 | ||
a7b65cdc | 327 | |
19bc17a9 | 328 | void |
4b10dd6c | 329 | write_all_categories (struct localedef_t *definitions, |
a7b65cdc | 330 | const struct charmap_t *charmap, const char *locname, |
75cd5204 | 331 | const char *output_path) |
19bc17a9 | 332 | { |
4b10dd6c UD |
333 | int cnt; |
334 | ||
335 | for (cnt = 0; cnt < sizeof (write_funcs) / sizeof (write_funcs[0]); ++cnt) | |
b9eb05d6 | 336 | if (write_funcs[cnt] != NULL) |
4b10dd6c | 337 | write_funcs[cnt] (definitions, charmap, output_path); |
a7b65cdc UD |
338 | |
339 | if (! no_archive) | |
340 | { | |
341 | /* The data has to be added to the archive. Do this now. */ | |
342 | struct locarhandle ah; | |
343 | ||
344 | /* Open the archive. This call never returns if we cannot | |
345 | successfully open the archive. */ | |
484c12fb | 346 | ah.fname = NULL; |
b2bffca2 | 347 | open_archive (&ah, false); |
a7b65cdc UD |
348 | |
349 | if (add_locale_to_archive (&ah, locname, to_archive, true) != 0) | |
350 | error (EXIT_FAILURE, errno, _("cannot add to locale archive")); | |
351 | ||
352 | /* We are done. */ | |
353 | close_archive (&ah); | |
354 | } | |
19bc17a9 RM |
355 | } |
356 | ||
a7b65cdc | 357 | |
aac0e8c4 UD |
358 | /* Return a NULL terminated list of the directories next to output_path |
359 | that have the same owner, group, permissions and device as output_path. */ | |
360 | static const char ** | |
361 | siblings_uncached (const char *output_path) | |
362 | { | |
363 | size_t len; | |
364 | char *base, *p; | |
4c0fe6fe | 365 | struct stat64 output_stat; |
aac0e8c4 UD |
366 | DIR *dirp; |
367 | int nelems; | |
368 | const char **elems; | |
369 | ||
370 | /* Remove trailing slashes and trailing pathname component. */ | |
371 | len = strlen (output_path); | |
372 | base = (char *) alloca (len); | |
373 | memcpy (base, output_path, len); | |
374 | p = base + len; | |
375 | while (p > base && p[-1] == '/') | |
376 | p--; | |
377 | if (p == base) | |
378 | return NULL; | |
379 | do | |
380 | p--; | |
381 | while (p > base && p[-1] != '/'); | |
382 | if (p == base) | |
383 | return NULL; | |
384 | *--p = '\0'; | |
385 | len = p - base; | |
386 | ||
387 | /* Get the properties of output_path. */ | |
4c0fe6fe | 388 | if (lstat64 (output_path, &output_stat) < 0 || !S_ISDIR (output_stat.st_mode)) |
aac0e8c4 UD |
389 | return NULL; |
390 | ||
391 | /* Iterate through the directories in base directory. */ | |
392 | dirp = opendir (base); | |
393 | if (dirp == NULL) | |
394 | return NULL; | |
395 | nelems = 0; | |
396 | elems = NULL; | |
397 | for (;;) | |
398 | { | |
9ef0a840 | 399 | struct dirent64 *other_dentry; |
aac0e8c4 UD |
400 | const char *other_name; |
401 | char *other_path; | |
4c0fe6fe | 402 | struct stat64 other_stat; |
aac0e8c4 | 403 | |
9ef0a840 | 404 | other_dentry = readdir64 (dirp); |
aac0e8c4 UD |
405 | if (other_dentry == NULL) |
406 | break; | |
407 | ||
408 | other_name = other_dentry->d_name; | |
409 | if (strcmp (other_name, ".") == 0 || strcmp (other_name, "..") == 0) | |
410 | continue; | |
19bc17a9 | 411 | |
aac0e8c4 UD |
412 | other_path = (char *) xmalloc (len + 1 + strlen (other_name) + 2); |
413 | memcpy (other_path, base, len); | |
414 | other_path[len] = '/'; | |
415 | strcpy (other_path + len + 1, other_name); | |
416 | ||
4c0fe6fe | 417 | if (lstat64 (other_path, &other_stat) >= 0 |
aac0e8c4 UD |
418 | && S_ISDIR (other_stat.st_mode) |
419 | && other_stat.st_uid == output_stat.st_uid | |
420 | && other_stat.st_gid == output_stat.st_gid | |
421 | && other_stat.st_mode == output_stat.st_mode | |
422 | && other_stat.st_dev == output_stat.st_dev) | |
423 | { | |
424 | /* Found a subdirectory. Add a trailing slash and store it. */ | |
425 | p = other_path + len + 1 + strlen (other_name); | |
426 | *p++ = '/'; | |
427 | *p = '\0'; | |
428 | elems = (const char **) xrealloc ((char *) elems, | |
429 | (nelems + 2) * sizeof (char **)); | |
430 | elems[nelems++] = other_path; | |
431 | } | |
432 | else | |
433 | free (other_path); | |
434 | } | |
435 | closedir (dirp); | |
436 | ||
437 | if (elems != NULL) | |
438 | elems[nelems] = NULL; | |
439 | return elems; | |
440 | } | |
441 | ||
a7b65cdc | 442 | |
aac0e8c4 UD |
443 | /* Return a NULL terminated list of the directories next to output_path |
444 | that have the same owner, group, permissions and device as output_path. | |
445 | Cache the result for future calls. */ | |
446 | static const char ** | |
447 | siblings (const char *output_path) | |
448 | { | |
449 | static const char *last_output_path; | |
450 | static const char **last_result; | |
451 | ||
452 | if (output_path != last_output_path) | |
453 | { | |
454 | if (last_result != NULL) | |
455 | { | |
456 | const char **p; | |
457 | ||
458 | for (p = last_result; *p != NULL; p++) | |
459 | free ((char *) *p); | |
460 | free (last_result); | |
461 | } | |
462 | ||
463 | last_output_path = output_path; | |
464 | last_result = siblings_uncached (output_path); | |
465 | } | |
466 | return last_result; | |
467 | } | |
468 | ||
a7b65cdc | 469 | |
aac0e8c4 UD |
470 | /* Read as many bytes from a file descriptor as possible. */ |
471 | static ssize_t | |
472 | full_read (int fd, void *bufarea, size_t nbyte) | |
473 | { | |
474 | char *buf = (char *) bufarea; | |
475 | ||
476 | while (nbyte > 0) | |
477 | { | |
478 | ssize_t retval = read (fd, buf, nbyte); | |
479 | ||
480 | if (retval == 0) | |
481 | break; | |
482 | else if (retval > 0) | |
483 | { | |
484 | buf += retval; | |
485 | nbyte -= retval; | |
486 | } | |
487 | else if (errno != EINTR) | |
488 | return retval; | |
489 | } | |
490 | return buf - (char *) bufarea; | |
491 | } | |
492 | ||
a7b65cdc | 493 | |
aac0e8c4 UD |
494 | /* Compare the contents of two regular files of the same size. Return 0 |
495 | if they are equal, 1 if they are different, or -1 if an error occurs. */ | |
496 | static int | |
497 | compare_files (const char *filename1, const char *filename2, size_t size, | |
498 | size_t blocksize) | |
499 | { | |
500 | int fd1, fd2; | |
501 | int ret = -1; | |
502 | ||
503 | fd1 = open (filename1, O_RDONLY); | |
504 | if (fd1 >= 0) | |
505 | { | |
506 | fd2 = open (filename2, O_RDONLY); | |
507 | if (fd2 >= 0) | |
508 | { | |
509 | char *buf1 = (char *) xmalloc (2 * blocksize); | |
510 | char *buf2 = buf1 + blocksize; | |
511 | ||
512 | ret = 0; | |
513 | while (size > 0) | |
514 | { | |
515 | size_t bytes = (size < blocksize ? size : blocksize); | |
516 | ||
517 | if (full_read (fd1, buf1, bytes) < (ssize_t) bytes) | |
518 | { | |
519 | ret = -1; | |
520 | break; | |
521 | } | |
522 | if (full_read (fd2, buf2, bytes) < (ssize_t) bytes) | |
523 | { | |
524 | ret = -1; | |
525 | break; | |
526 | } | |
527 | if (memcmp (buf1, buf2, bytes) != 0) | |
528 | { | |
529 | ret = 1; | |
530 | break; | |
531 | } | |
532 | size -= bytes; | |
533 | } | |
534 | ||
535 | free (buf1); | |
536 | close (fd2); | |
537 | } | |
538 | close (fd1); | |
539 | } | |
540 | return ret; | |
541 | } | |
542 | ||
6055173a JM |
543 | /* True if the locale files use the opposite endianness to the |
544 | machine running localedef. */ | |
545 | bool swap_endianness_p; | |
546 | ||
1ecbb381 RS |
547 | /* When called outside a start_locale_structure/end_locale_structure |
548 | or start_locale_prelude/end_locale_prelude block, record that the | |
549 | next byte in FILE's obstack will be the first byte of a new element. | |
550 | Do likewise for the first call inside a start_locale_structure/ | |
551 | end_locale_structure block. */ | |
552 | static void | |
553 | record_offset (struct locale_file *file) | |
554 | { | |
555 | if (file->structure_stage < 2) | |
556 | { | |
557 | assert (file->next_element < file->n_elements); | |
558 | file->offsets[file->next_element++] | |
559 | = (obstack_object_size (&file->data) | |
560 | + (file->n_elements + 2) * sizeof (uint32_t)); | |
561 | if (file->structure_stage == 1) | |
562 | file->structure_stage = 2; | |
563 | } | |
564 | } | |
565 | ||
566 | /* Initialize FILE for a new output file. N_ELEMENTS is the number | |
567 | of elements in the file. */ | |
568 | void | |
569 | init_locale_data (struct locale_file *file, size_t n_elements) | |
570 | { | |
571 | file->n_elements = n_elements; | |
572 | file->next_element = 0; | |
573 | file->offsets = xmalloc (sizeof (uint32_t) * n_elements); | |
574 | obstack_init (&file->data); | |
575 | file->structure_stage = 0; | |
576 | } | |
577 | ||
578 | /* Align the size of FILE's obstack object to BOUNDARY bytes. */ | |
579 | void | |
580 | align_locale_data (struct locale_file *file, size_t boundary) | |
581 | { | |
582 | size_t size = -obstack_object_size (&file->data) & (boundary - 1); | |
583 | obstack_blank (&file->data, size); | |
584 | memset (obstack_next_free (&file->data) - size, 0, size); | |
585 | } | |
586 | ||
587 | /* Record that FILE's next element contains no data. */ | |
588 | void | |
589 | add_locale_empty (struct locale_file *file) | |
590 | { | |
591 | record_offset (file); | |
592 | } | |
a7b65cdc | 593 | |
1ecbb381 RS |
594 | /* Record that FILE's next element consists of SIZE bytes starting at DATA. */ |
595 | void | |
596 | add_locale_raw_data (struct locale_file *file, const void *data, size_t size) | |
597 | { | |
598 | record_offset (file); | |
599 | obstack_grow (&file->data, data, size); | |
600 | } | |
601 | ||
602 | /* Finish the current object on OBSTACK and use it as the data for FILE's | |
603 | next element. */ | |
604 | void | |
605 | add_locale_raw_obstack (struct locale_file *file, struct obstack *obstack) | |
606 | { | |
607 | size_t size = obstack_object_size (obstack); | |
608 | record_offset (file); | |
609 | obstack_grow (&file->data, obstack_finish (obstack), size); | |
610 | } | |
611 | ||
612 | /* Use STRING as FILE's next element. */ | |
613 | void | |
614 | add_locale_string (struct locale_file *file, const char *string) | |
615 | { | |
616 | record_offset (file); | |
617 | obstack_grow (&file->data, string, strlen (string) + 1); | |
618 | } | |
619 | ||
620 | /* Likewise for wide strings. */ | |
621 | void | |
622 | add_locale_wstring (struct locale_file *file, const uint32_t *string) | |
623 | { | |
624 | add_locale_uint32_array (file, string, wcslen ((const wchar_t *) string) + 1); | |
625 | } | |
626 | ||
627 | /* Record that FILE's next element is the 32-bit integer VALUE. */ | |
628 | void | |
629 | add_locale_uint32 (struct locale_file *file, uint32_t value) | |
630 | { | |
7602d070 | 631 | align_locale_data (file, LOCFILE_ALIGN); |
1ecbb381 | 632 | record_offset (file); |
6055173a | 633 | value = maybe_swap_uint32 (value); |
1ecbb381 RS |
634 | obstack_grow (&file->data, &value, sizeof (value)); |
635 | } | |
636 | ||
637 | /* Record that FILE's next element is an array of N_ELEMS integers | |
638 | starting at DATA. */ | |
639 | void | |
640 | add_locale_uint32_array (struct locale_file *file, | |
641 | const uint32_t *data, size_t n_elems) | |
642 | { | |
7602d070 | 643 | align_locale_data (file, LOCFILE_ALIGN); |
1ecbb381 RS |
644 | record_offset (file); |
645 | obstack_grow (&file->data, data, n_elems * sizeof (uint32_t)); | |
6055173a | 646 | maybe_swap_uint32_obstack (&file->data, n_elems); |
1ecbb381 RS |
647 | } |
648 | ||
649 | /* Record that FILE's next element is the single byte given by VALUE. */ | |
650 | void | |
651 | add_locale_char (struct locale_file *file, char value) | |
652 | { | |
653 | record_offset (file); | |
654 | obstack_1grow (&file->data, value); | |
655 | } | |
656 | ||
657 | /* Start building an element that contains several different pieces of data. | |
658 | Subsequent calls to add_locale_* will add data to the same element up | |
659 | till the next call to end_locale_structure. The element's alignment | |
660 | is dictated by the first piece of data added to it. */ | |
661 | void | |
662 | start_locale_structure (struct locale_file *file) | |
663 | { | |
664 | assert (file->structure_stage == 0); | |
665 | file->structure_stage = 1; | |
666 | } | |
667 | ||
668 | /* Finish a structure element that was started by start_locale_structure. | |
669 | Empty structures are OK and behave like add_locale_empty. */ | |
670 | void | |
671 | end_locale_structure (struct locale_file *file) | |
672 | { | |
673 | record_offset (file); | |
674 | assert (file->structure_stage == 2); | |
675 | file->structure_stage = 0; | |
676 | } | |
677 | ||
678 | /* Start building data that goes before the next element's recorded offset. | |
679 | Subsequent calls to add_locale_* will add data to the file without | |
680 | treating any of it as the start of a new element. Calling | |
681 | end_locale_prelude switches back to the usual behavior. */ | |
682 | void | |
683 | start_locale_prelude (struct locale_file *file) | |
684 | { | |
685 | assert (file->structure_stage == 0); | |
686 | file->structure_stage = 3; | |
687 | } | |
688 | ||
689 | /* End a block started by start_locale_prelude. */ | |
690 | void | |
691 | end_locale_prelude (struct locale_file *file) | |
692 | { | |
693 | assert (file->structure_stage == 3); | |
694 | file->structure_stage = 0; | |
695 | } | |
696 | ||
697 | /* Write a locale file, with contents given by FILE. */ | |
19bc17a9 | 698 | void |
a7b65cdc | 699 | write_locale_data (const char *output_path, int catidx, const char *category, |
1ecbb381 | 700 | struct locale_file *file) |
19bc17a9 | 701 | { |
c199a24f | 702 | size_t cnt, step, maxiov; |
19bc17a9 RM |
703 | int fd; |
704 | char *fname; | |
8cebd4ff | 705 | const char **other_paths = NULL; |
1ecbb381 RS |
706 | uint32_t header[2]; |
707 | size_t n_elem; | |
708 | struct iovec vec[3]; | |
709 | ||
710 | assert (file->n_elements == file->next_element); | |
711 | header[0] = LIMAGIC (catidx); | |
712 | header[1] = file->n_elements; | |
713 | vec[0].iov_len = sizeof (header); | |
714 | vec[0].iov_base = header; | |
715 | vec[1].iov_len = sizeof (uint32_t) * file->n_elements; | |
716 | vec[1].iov_base = file->offsets; | |
717 | vec[2].iov_len = obstack_object_size (&file->data); | |
718 | vec[2].iov_base = obstack_finish (&file->data); | |
6055173a JM |
719 | maybe_swap_uint32_array (vec[0].iov_base, 2); |
720 | maybe_swap_uint32_array (vec[1].iov_base, file->n_elements); | |
1ecbb381 | 721 | n_elem = 3; |
a7b65cdc UD |
722 | if (! no_archive) |
723 | { | |
724 | /* The data will be added to the archive. For now we simply | |
725 | generate the image which will be written. First determine | |
726 | the size. */ | |
727 | int cnt; | |
728 | void *endp; | |
729 | ||
730 | to_archive[catidx].size = 0; | |
731 | for (cnt = 0; cnt < n_elem; ++cnt) | |
732 | to_archive[catidx].size += vec[cnt].iov_len; | |
733 | ||
734 | /* Allocate the memory for it. */ | |
735 | to_archive[catidx].addr = xmalloc (to_archive[catidx].size); | |
736 | ||
737 | /* Fill it in. */ | |
738 | for (cnt = 0, endp = to_archive[catidx].addr; cnt < n_elem; ++cnt) | |
739 | endp = mempcpy (endp, vec[cnt].iov_base, vec[cnt].iov_len); | |
740 | ||
741 | /* Compute the MD5 sum for the data. */ | |
742 | __md5_buffer (to_archive[catidx].addr, to_archive[catidx].size, | |
743 | to_archive[catidx].sum); | |
744 | ||
745 | return; | |
746 | } | |
747 | ||
aac0e8c4 | 748 | fname = xmalloc (strlen (output_path) + 2 * strlen (category) + 7); |
036cc82f RM |
749 | |
750 | /* Normally we write to the directory pointed to by the OUTPUT_PATH. | |
751 | But for LC_MESSAGES we have to take care for the translation | |
752 | data. This means we need to have a directory LC_MESSAGES in | |
753 | which we place the file under the name SYS_LC_MESSAGES. */ | |
5e6160ed | 754 | sprintf (fname, "%s%s", output_path, category); |
601d2942 | 755 | fd = -2; |
036cc82f | 756 | if (strcmp (category, "LC_MESSAGES") == 0) |
036cc82f | 757 | { |
4c0fe6fe | 758 | struct stat64 st; |
ce7a5ef4 | 759 | |
4c0fe6fe | 760 | if (stat64 (fname, &st) < 0) |
ce7a5ef4 | 761 | { |
601d2942 | 762 | if (mkdir (fname, 0777) >= 0) |
ce7a5ef4 RM |
763 | { |
764 | fd = -1; | |
765 | errno = EISDIR; | |
766 | } | |
767 | } | |
601d2942 | 768 | else if (!S_ISREG (st.st_mode)) |
ce7a5ef4 RM |
769 | { |
770 | fd = -1; | |
771 | errno = EISDIR; | |
772 | } | |
036cc82f | 773 | } |
601d2942 UD |
774 | |
775 | /* Create the locale file with nlinks == 1; this avoids crashing processes | |
aac0e8c4 UD |
776 | which currently use the locale and damaging files belonging to other |
777 | locales as well. */ | |
601d2942 UD |
778 | if (fd == -2) |
779 | { | |
780 | unlink (fname); | |
781 | fd = creat (fname, 0666); | |
782 | } | |
036cc82f | 783 | |
19bc17a9 RM |
784 | if (fd == -1) |
785 | { | |
786 | int save_err = errno; | |
787 | ||
788 | if (errno == EISDIR) | |
789 | { | |
601d2942 UD |
790 | sprintf (fname, "%1$s%2$s/SYS_%2$s", output_path, category); |
791 | unlink (fname); | |
19bc17a9 RM |
792 | fd = creat (fname, 0666); |
793 | if (fd == -1) | |
794 | save_err = errno; | |
795 | } | |
796 | ||
880f421f | 797 | if (fd == -1) |
19bc17a9 | 798 | { |
f16491eb CD |
799 | record_error (0, save_err, _("\ |
800 | cannot open output file `%s' for category `%s'"), fname, category); | |
601d2942 | 801 | free (fname); |
19bc17a9 RM |
802 | return; |
803 | } | |
804 | } | |
19bc17a9 | 805 | |
c199a24f MB |
806 | #ifdef UIO_MAXIOV |
807 | maxiov = UIO_MAXIOV; | |
808 | #else | |
809 | maxiov = sysconf (_SC_UIO_MAXIOV); | |
810 | #endif | |
811 | ||
19bc17a9 RM |
812 | /* Write the data using writev. But we must take care for the |
813 | limitation of the implementation. */ | |
814 | for (cnt = 0; cnt < n_elem; cnt += step) | |
815 | { | |
c199a24f MB |
816 | step = n_elem - cnt; |
817 | if (maxiov > 0) | |
818 | step = MIN (maxiov, step); | |
19bc17a9 | 819 | |
880f421f | 820 | if (writev (fd, &vec[cnt], step) < 0) |
19bc17a9 | 821 | { |
f16491eb CD |
822 | record_error (0, errno, _("\ |
823 | failure while writing data for category `%s'"), category); | |
19bc17a9 RM |
824 | break; |
825 | } | |
826 | } | |
827 | ||
828 | close (fd); | |
aac0e8c4 | 829 | |
8cebd4ff CD |
830 | /* Compare the file with the locale data files for the same category |
831 | in other locales, and see if we can reuse it, to save disk space. | |
832 | If the user specified --no-hard-links to localedef then hard_links | |
833 | is false, other_paths remains NULL and we skip the optimization | |
834 | below. The use of --no-hard-links is distribution specific since | |
835 | some distros have post-processing hard-link steps and so doing this | |
836 | here is a waste of time. Worse than a waste of time in rpm-based | |
837 | distributions it can result in build determinism issues from | |
838 | build-to-build since some files may get a hard link in one pass but | |
839 | not in another (if the files happened to be created in parallel). */ | |
840 | if (hard_links) | |
841 | other_paths = siblings (output_path); | |
842 | ||
843 | /* If there are other paths, then walk the sibling paths looking for | |
844 | files with the same content so we can hard link and reduce disk | |
845 | space usage. */ | |
aac0e8c4 UD |
846 | if (other_paths != NULL) |
847 | { | |
4c0fe6fe | 848 | struct stat64 fname_stat; |
aac0e8c4 | 849 | |
4c0fe6fe | 850 | if (lstat64 (fname, &fname_stat) >= 0 |
aac0e8c4 UD |
851 | && S_ISREG (fname_stat.st_mode)) |
852 | { | |
853 | const char *fname_tail = fname + strlen (output_path); | |
854 | const char **other_p; | |
855 | int seen_count; | |
856 | ino_t *seen_inodes; | |
857 | ||
858 | seen_count = 0; | |
859 | for (other_p = other_paths; *other_p; other_p++) | |
860 | seen_count++; | |
861 | seen_inodes = (ino_t *) xmalloc (seen_count * sizeof (ino_t)); | |
862 | seen_count = 0; | |
863 | ||
864 | for (other_p = other_paths; *other_p; other_p++) | |
865 | { | |
866 | const char *other_path = *other_p; | |
867 | size_t other_path_len = strlen (other_path); | |
868 | char *other_fname; | |
4c0fe6fe | 869 | struct stat64 other_fname_stat; |
aac0e8c4 UD |
870 | |
871 | other_fname = | |
872 | (char *) xmalloc (other_path_len + strlen (fname_tail) + 1); | |
873 | memcpy (other_fname, other_path, other_path_len); | |
874 | strcpy (other_fname + other_path_len, fname_tail); | |
875 | ||
4c0fe6fe | 876 | if (lstat64 (other_fname, &other_fname_stat) >= 0 |
aac0e8c4 UD |
877 | && S_ISREG (other_fname_stat.st_mode) |
878 | /* Consider only files on the same device. | |
879 | Otherwise hard linking won't work anyway. */ | |
880 | && other_fname_stat.st_dev == fname_stat.st_dev | |
881 | /* Consider only files with the same permissions. | |
882 | Otherwise there are security risks. */ | |
883 | && other_fname_stat.st_uid == fname_stat.st_uid | |
884 | && other_fname_stat.st_gid == fname_stat.st_gid | |
885 | && other_fname_stat.st_mode == fname_stat.st_mode | |
886 | /* Don't compare fname with itself. */ | |
887 | && other_fname_stat.st_ino != fname_stat.st_ino | |
888 | /* Files must have the same size, otherwise they | |
889 | cannot be the same. */ | |
890 | && other_fname_stat.st_size == fname_stat.st_size) | |
891 | { | |
892 | /* Skip this file if we have already read it (under a | |
893 | different name). */ | |
894 | int i; | |
895 | ||
896 | for (i = seen_count - 1; i >= 0; i--) | |
897 | if (seen_inodes[i] == other_fname_stat.st_ino) | |
898 | break; | |
899 | if (i < 0) | |
900 | { | |
901 | /* Now compare fname and other_fname for real. */ | |
902 | blksize_t blocksize; | |
903 | ||
904 | #ifdef _STATBUF_ST_BLKSIZE | |
905 | blocksize = MAX (fname_stat.st_blksize, | |
906 | other_fname_stat.st_blksize); | |
907 | if (blocksize > 8 * 1024) | |
908 | blocksize = 8 * 1024; | |
909 | #else | |
910 | blocksize = 8 * 1024; | |
911 | #endif | |
912 | ||
913 | if (compare_files (fname, other_fname, | |
914 | fname_stat.st_size, blocksize) == 0) | |
915 | { | |
916 | /* Found! other_fname is identical to fname. */ | |
917 | /* Link other_fname to fname. But use a temporary | |
918 | file, in case hard links don't work on the | |
919 | particular filesystem. */ | |
920 | char * tmp_fname = | |
921 | (char *) xmalloc (strlen (fname) + 4 + 1); | |
922 | ||
a7b65cdc | 923 | strcpy (stpcpy (tmp_fname, fname), ".tmp"); |
aac0e8c4 UD |
924 | |
925 | if (link (other_fname, tmp_fname) >= 0) | |
926 | { | |
927 | unlink (fname); | |
928 | if (rename (tmp_fname, fname) < 0) | |
929 | { | |
f16491eb CD |
930 | record_error (0, errno, _("\ |
931 | cannot create output file `%s' for category `%s'"), fname, category); | |
aac0e8c4 UD |
932 | } |
933 | free (tmp_fname); | |
934 | free (other_fname); | |
935 | break; | |
936 | } | |
937 | free (tmp_fname); | |
938 | } | |
939 | ||
940 | /* Don't compare with this file a second time. */ | |
941 | seen_inodes[seen_count++] = other_fname_stat.st_ino; | |
942 | } | |
943 | } | |
944 | free (other_fname); | |
945 | } | |
946 | free (seen_inodes); | |
947 | } | |
948 | } | |
949 | ||
950 | free (fname); | |
19bc17a9 | 951 | } |
25337753 UD |
952 | |
953 | ||
954 | /* General handling of `copy'. */ | |
955 | void | |
956 | handle_copy (struct linereader *ldfile, const struct charmap_t *charmap, | |
957 | const char *repertoire_name, struct localedef_t *result, | |
958 | enum token_t token, int locale, const char *locale_name, | |
959 | int ignore_content) | |
960 | { | |
961 | struct token *now; | |
962 | int warned = 0; | |
963 | ||
964 | now = lr_token (ldfile, charmap, result, NULL, verbose); | |
965 | if (now->tok != tok_string) | |
1d20f7f8 | 966 | lr_error (ldfile, _("expecting string argument for `copy'")); |
25337753 UD |
967 | else if (!ignore_content) |
968 | { | |
969 | if (now->val.str.startmb == NULL) | |
970 | lr_error (ldfile, _("\ | |
971 | locale name should consist only of portable characters")); | |
972 | else | |
973 | { | |
974 | (void) add_to_readlist (locale, now->val.str.startmb, | |
975 | repertoire_name, 1, NULL); | |
976 | result->copy_name[locale] = now->val.str.startmb; | |
977 | } | |
978 | } | |
979 | ||
980 | lr_ignore_rest (ldfile, now->tok == tok_string); | |
981 | ||
982 | /* The rest of the line must be empty and the next keyword must be | |
983 | `END xxx'. */ | |
984 | while ((now = lr_token (ldfile, charmap, result, NULL, verbose))->tok | |
985 | != tok_end && now->tok != tok_eof) | |
986 | { | |
987 | if (warned == 0) | |
988 | { | |
989 | lr_error (ldfile, _("\ | |
990 | no other keyword shall be specified when `copy' is used")); | |
991 | warned = 1; | |
992 | } | |
993 | ||
994 | lr_ignore_rest (ldfile, 0); | |
995 | } | |
996 | ||
997 | if (now->tok != tok_eof) | |
998 | { | |
999 | /* Handle `END xxx'. */ | |
1000 | now = lr_token (ldfile, charmap, result, NULL, verbose); | |
1001 | ||
1002 | if (now->tok != token) | |
1003 | lr_error (ldfile, _("\ | |
1004 | `%1$s' definition does not end with `END %1$s'"), locale_name); | |
1005 | ||
1006 | lr_ignore_rest (ldfile, now->tok == token); | |
1007 | } | |
1008 | else | |
1009 | /* When we come here we reached the end of the file. */ | |
1010 | lr_error (ldfile, _("%s: premature end of file"), locale_name); | |
1011 | } |