1 | /* Copyright (C) 1996-2019 Free Software Foundation, Inc. |
2 | This file is part of the GNU C Library. |
3 | Contributed by Ulrich Drepper <drepper@gnu.org>, 1996. |
4 | |
5 | This program is free software; you can redistribute it and/or modify |
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. |
9 | |
10 | This program 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 |
13 | GNU General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU General Public License |
16 | along with this program; if not, see <http://www.gnu.org/licenses/>. */ |
17 | |
18 | #ifdef HAVE_CONFIG_H |
19 | # include <config.h> |
20 | #endif |
21 | |
22 | #include <dirent.h> |
23 | #include <errno.h> |
24 | #include <fcntl.h> |
25 | #include <stdbool.h> |
26 | #include <stdlib.h> |
27 | #include <string.h> |
28 | #include <unistd.h> |
29 | #include <sys/param.h> |
30 | #include <sys/stat.h> |
31 | #include <assert.h> |
32 | #include <wchar.h> |
33 | |
34 | #include "../../crypt/md5.h" |
35 | #include "localedef.h" |
36 | #include "localeinfo.h" |
37 | #include "locfile.h" |
38 | #include "simple-hash.h" |
39 | |
40 | #include "locfile-kw.h" |
41 | |
42 | #define obstack_chunk_alloc xmalloc |
43 | #define obstack_chunk_free free |
44 | |
45 | /* Temporary storage of the locale data before writing it to the archive. */ |
46 | static locale_data_t to_archive; |
47 | |
48 | |
49 | int |
50 | locfile_read (struct localedef_t *result, const struct charmap_t *charmap) |
51 | { |
52 | const char *filename = result->name; |
53 | const char *repertoire_name = result->repertoire_name; |
54 | int locale_mask = result->needed & ~result->avail; |
55 | struct linereader *ldfile; |
56 | int not_here = ALL_LOCALES; |
57 | |
58 | /* If no repertoire name was specified use the global one. */ |
59 | if (repertoire_name == NULL) |
60 | repertoire_name = repertoire_global; |
61 | |
62 | /* Open the locale definition file. */ |
63 | ldfile = lr_open (filename, locfile_hash); |
64 | if (ldfile == NULL) |
65 | { |
66 | if (filename != NULL && filename[0] != '/') |
67 | { |
68 | char *i18npath = getenv ("I18NPATH" ); |
69 | if (i18npath != NULL && *i18npath != '\0') |
70 | { |
71 | const size_t pathlen = strlen (i18npath); |
72 | char i18npathbuf[pathlen + 1]; |
73 | char path[strlen (filename) + 1 + pathlen |
74 | + sizeof ("/locales/" ) - 1]; |
75 | char *next; |
76 | i18npath = memcpy (i18npathbuf, i18npath, pathlen + 1); |
77 | |
78 | while (ldfile == NULL |
79 | && (next = strsep (&i18npath, ":" )) != NULL) |
80 | { |
81 | stpcpy (stpcpy (stpcpy (path, next), "/locales/" ), filename); |
82 | |
83 | ldfile = lr_open (path, locfile_hash); |
84 | |
85 | if (ldfile == NULL) |
86 | { |
87 | stpcpy (stpcpy (stpcpy (path, next), "/" ), filename); |
88 | |
89 | ldfile = lr_open (path, locfile_hash); |
90 | } |
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 | } |
102 | } |
103 | |
104 | if (ldfile == NULL) |
105 | return 1; |
106 | } |
107 | |
108 | /* Parse locale definition file and store result in RESULT. */ |
109 | while (1) |
110 | { |
111 | struct token *now = lr_token (ldfile, charmap, NULL, NULL, verbose); |
112 | enum token_t nowtok = now->tok; |
113 | struct token *arg; |
114 | |
115 | if (nowtok == tok_eof) |
116 | break; |
117 | |
118 | if (nowtok == tok_eol) |
119 | /* Ignore empty lines. */ |
120 | continue; |
121 | |
122 | switch (nowtok) |
123 | { |
124 | case tok_escape_char: |
125 | case tok_comment_char: |
126 | /* We need an argument. */ |
127 | arg = lr_token (ldfile, charmap, NULL, NULL, verbose); |
128 | |
129 | if (arg->tok != tok_ident) |
130 | { |
131 | SYNTAX_ERROR (_("bad argument" )); |
132 | continue; |
133 | } |
134 | |
135 | if (arg->val.str.lenmb != 1) |
136 | { |
137 | lr_error (ldfile, _("\ |
138 | argument to `%s' must be a single character" ), |
139 | nowtok == tok_escape_char |
140 | ? "escape_char" : "comment_char" ); |
141 | |
142 | lr_ignore_rest (ldfile, 0); |
143 | continue; |
144 | } |
145 | |
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; |
151 | |
152 | case tok_repertoiremap: |
153 | /* We need an argument. */ |
154 | arg = lr_token (ldfile, charmap, NULL, NULL, verbose); |
155 | |
156 | if (arg->tok != tok_ident) |
157 | { |
158 | SYNTAX_ERROR (_("bad argument" )); |
159 | continue; |
160 | } |
161 | |
162 | if (repertoire_name == NULL) |
163 | { |
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; |
169 | } |
170 | break; |
171 | |
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; |
176 | not_here ^= CTYPE_LOCALE; |
177 | continue; |
178 | |
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; |
183 | not_here ^= COLLATE_LOCALE; |
184 | continue; |
185 | |
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; |
190 | not_here ^= MONETARY_LOCALE; |
191 | continue; |
192 | |
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; |
197 | not_here ^= NUMERIC_LOCALE; |
198 | continue; |
199 | |
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; |
204 | not_here ^= TIME_LOCALE; |
205 | continue; |
206 | |
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; |
211 | not_here ^= MESSAGES_LOCALE; |
212 | continue; |
213 | |
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; |
218 | not_here ^= PAPER_LOCALE; |
219 | continue; |
220 | |
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; |
225 | not_here ^= NAME_LOCALE; |
226 | continue; |
227 | |
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; |
232 | not_here ^= ADDRESS_LOCALE; |
233 | continue; |
234 | |
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; |
239 | not_here ^= TELEPHONE_LOCALE; |
240 | continue; |
241 | |
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; |
246 | not_here ^= MEASUREMENT_LOCALE; |
247 | continue; |
248 | |
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; |
253 | not_here ^= IDENTIFICATION_LOCALE; |
254 | continue; |
255 | |
256 | default: |
257 | SYNTAX_ERROR (_("\ |
258 | syntax error: not inside a locale definition section" )); |
259 | continue; |
260 | } |
261 | |
262 | /* The rest of the line must be empty. */ |
263 | lr_ignore_rest (ldfile, 1); |
264 | } |
265 | |
266 | /* We read all of the file. */ |
267 | lr_close (ldfile); |
268 | |
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 | |
273 | return 0; |
274 | } |
275 | |
276 | |
277 | /* Semantic checking of locale specifications. */ |
278 | |
279 | static void (*const check_funcs[]) (struct localedef_t *, |
280 | const struct charmap_t *) = |
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 | |
296 | void |
297 | check_all_categories (struct localedef_t *definitions, |
298 | const struct charmap_t *charmap) |
299 | { |
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); |
305 | } |
306 | |
307 | |
308 | /* Writing the locale data files. All files use the same output_path. */ |
309 | |
310 | static void (*const write_funcs[]) (struct localedef_t *, |
311 | const struct charmap_t *, const char *) = |
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 | |
327 | |
328 | void |
329 | write_all_categories (struct localedef_t *definitions, |
330 | const struct charmap_t *charmap, const char *locname, |
331 | const char *output_path) |
332 | { |
333 | int cnt; |
334 | |
335 | for (cnt = 0; cnt < sizeof (write_funcs) / sizeof (write_funcs[0]); ++cnt) |
336 | if (write_funcs[cnt] != NULL) |
337 | write_funcs[cnt] (definitions, charmap, output_path); |
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. */ |
346 | ah.fname = NULL; |
347 | open_archive (&ah, false); |
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 | } |
355 | } |
356 | |
357 | |
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; |
365 | struct stat64 output_stat; |
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. */ |
388 | if (lstat64 (output_path, &output_stat) < 0 || !S_ISDIR (output_stat.st_mode)) |
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 | { |
399 | struct dirent64 *other_dentry; |
400 | const char *other_name; |
401 | char *other_path; |
402 | struct stat64 other_stat; |
403 | |
404 | other_dentry = readdir64 (dirp); |
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; |
411 | |
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 | |
417 | if (lstat64 (other_path, &other_stat) >= 0 |
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 | |
442 | |
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 | |
469 | |
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 | |
493 | |
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 | |
543 | /* True if the locale files use the opposite endianness to the |
544 | machine running localedef. */ |
545 | bool swap_endianness_p; |
546 | |
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 | } |
593 | |
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 | { |
631 | align_locale_data (file, LOCFILE_ALIGN); |
632 | record_offset (file); |
633 | value = maybe_swap_uint32 (value); |
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 | { |
643 | align_locale_data (file, LOCFILE_ALIGN); |
644 | record_offset (file); |
645 | obstack_grow (&file->data, data, n_elems * sizeof (uint32_t)); |
646 | maybe_swap_uint32_obstack (&file->data, n_elems); |
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. */ |
698 | void |
699 | write_locale_data (const char *output_path, int catidx, const char *category, |
700 | struct locale_file *file) |
701 | { |
702 | size_t cnt, step, maxiov; |
703 | int fd; |
704 | char *fname; |
705 | const char **other_paths = NULL; |
706 | uint32_t [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); |
719 | maybe_swap_uint32_array (vec[0].iov_base, 2); |
720 | maybe_swap_uint32_array (vec[1].iov_base, file->n_elements); |
721 | n_elem = 3; |
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 | |
748 | fname = xmalloc (strlen (output_path) + 2 * strlen (category) + 7); |
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. */ |
754 | sprintf (fname, "%s%s" , output_path, category); |
755 | fd = -2; |
756 | if (strcmp (category, "LC_MESSAGES" ) == 0) |
757 | { |
758 | struct stat64 st; |
759 | |
760 | if (stat64 (fname, &st) < 0) |
761 | { |
762 | if (mkdir (fname, 0777) >= 0) |
763 | { |
764 | fd = -1; |
765 | errno = EISDIR; |
766 | } |
767 | } |
768 | else if (!S_ISREG (st.st_mode)) |
769 | { |
770 | fd = -1; |
771 | errno = EISDIR; |
772 | } |
773 | } |
774 | |
775 | /* Create the locale file with nlinks == 1; this avoids crashing processes |
776 | which currently use the locale and damaging files belonging to other |
777 | locales as well. */ |
778 | if (fd == -2) |
779 | { |
780 | unlink (fname); |
781 | fd = creat (fname, 0666); |
782 | } |
783 | |
784 | if (fd == -1) |
785 | { |
786 | int save_err = errno; |
787 | |
788 | if (errno == EISDIR) |
789 | { |
790 | sprintf (fname, "%1$s%2$s/SYS_%2$s" , output_path, category); |
791 | unlink (fname); |
792 | fd = creat (fname, 0666); |
793 | if (fd == -1) |
794 | save_err = errno; |
795 | } |
796 | |
797 | if (fd == -1) |
798 | { |
799 | record_error (0, save_err, _("\ |
800 | cannot open output file `%s' for category `%s'" ), fname, category); |
801 | free (fname); |
802 | return; |
803 | } |
804 | } |
805 | |
806 | #ifdef UIO_MAXIOV |
807 | maxiov = UIO_MAXIOV; |
808 | #else |
809 | maxiov = sysconf (_SC_UIO_MAXIOV); |
810 | #endif |
811 | |
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 | { |
816 | step = n_elem - cnt; |
817 | if (maxiov > 0) |
818 | step = MIN (maxiov, step); |
819 | |
820 | if (writev (fd, &vec[cnt], step) < 0) |
821 | { |
822 | record_error (0, errno, _("\ |
823 | failure while writing data for category `%s'" ), category); |
824 | break; |
825 | } |
826 | } |
827 | |
828 | close (fd); |
829 | |
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. */ |
846 | if (other_paths != NULL) |
847 | { |
848 | struct stat64 fname_stat; |
849 | |
850 | if (lstat64 (fname, &fname_stat) >= 0 |
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; |
869 | struct stat64 other_fname_stat; |
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 | |
876 | if (lstat64 (other_fname, &other_fname_stat) >= 0 |
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 | |
923 | strcpy (stpcpy (tmp_fname, fname), ".tmp" ); |
924 | |
925 | if (link (other_fname, tmp_fname) >= 0) |
926 | { |
927 | unlink (fname); |
928 | if (rename (tmp_fname, fname) < 0) |
929 | { |
930 | record_error (0, errno, _("\ |
931 | cannot create output file `%s' for category `%s'" ), fname, category); |
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); |
951 | } |
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) |
966 | lr_error (ldfile, _("expecting string argument for `copy'" )); |
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 | } |
1012 | |