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