]>
Commit | Line | Data |
---|---|---|
568035b7 | 1 | /* Copyright (C) 1996-2013 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 |
59ba27a6 | 16 | along with this program; if not, see <http://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 | { | |
87 | stpcpy (stpcpy (path, next), filename); | |
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. */ | |
b2bffca2 | 346 | open_archive (&ah, false); |
a7b65cdc UD |
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 | } | |
19bc17a9 RM |
354 | } |
355 | ||
a7b65cdc | 356 | |
aac0e8c4 UD |
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; | |
4c0fe6fe | 364 | struct stat64 output_stat; |
aac0e8c4 UD |
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. */ | |
4c0fe6fe | 387 | if (lstat64 (output_path, &output_stat) < 0 || !S_ISDIR (output_stat.st_mode)) |
aac0e8c4 UD |
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 | { | |
9ef0a840 | 398 | struct dirent64 *other_dentry; |
aac0e8c4 UD |
399 | const char *other_name; |
400 | char *other_path; | |
4c0fe6fe | 401 | struct stat64 other_stat; |
aac0e8c4 | 402 | |
9ef0a840 | 403 | other_dentry = readdir64 (dirp); |
aac0e8c4 UD |
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; | |
19bc17a9 | 410 | |
aac0e8c4 UD |
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 | ||
4c0fe6fe | 416 | if (lstat64 (other_path, &other_stat) >= 0 |
aac0e8c4 UD |
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 | ||
a7b65cdc | 441 | |
aac0e8c4 UD |
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 | ||
a7b65cdc | 468 | |
aac0e8c4 UD |
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 | ||
a7b65cdc | 492 | |
aac0e8c4 UD |
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 | ||
6055173a JM |
542 | /* True if the locale files use the opposite endianness to the |
543 | machine running localedef. */ | |
544 | bool swap_endianness_p; | |
545 | ||
1ecbb381 RS |
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 | } | |
a7b65cdc | 592 | |
1ecbb381 RS |
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 | { | |
7602d070 | 630 | align_locale_data (file, LOCFILE_ALIGN); |
1ecbb381 | 631 | record_offset (file); |
6055173a | 632 | value = maybe_swap_uint32 (value); |
1ecbb381 RS |
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 | { | |
7602d070 | 642 | align_locale_data (file, LOCFILE_ALIGN); |
1ecbb381 RS |
643 | record_offset (file); |
644 | obstack_grow (&file->data, data, n_elems * sizeof (uint32_t)); | |
6055173a | 645 | maybe_swap_uint32_obstack (&file->data, n_elems); |
1ecbb381 RS |
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. */ | |
19bc17a9 | 697 | void |
a7b65cdc | 698 | write_locale_data (const char *output_path, int catidx, const char *category, |
1ecbb381 | 699 | struct locale_file *file) |
19bc17a9 | 700 | { |
c199a24f | 701 | size_t cnt, step, maxiov; |
19bc17a9 RM |
702 | int fd; |
703 | char *fname; | |
aac0e8c4 | 704 | const char **other_paths; |
1ecbb381 RS |
705 | uint32_t header[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); | |
6055173a JM |
718 | maybe_swap_uint32_array (vec[0].iov_base, 2); |
719 | maybe_swap_uint32_array (vec[1].iov_base, file->n_elements); | |
1ecbb381 | 720 | n_elem = 3; |
a7b65cdc UD |
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 | ||
aac0e8c4 | 747 | fname = xmalloc (strlen (output_path) + 2 * strlen (category) + 7); |
036cc82f RM |
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. */ | |
5e6160ed | 753 | sprintf (fname, "%s%s", output_path, category); |
601d2942 | 754 | fd = -2; |
036cc82f | 755 | if (strcmp (category, "LC_MESSAGES") == 0) |
036cc82f | 756 | { |
4c0fe6fe | 757 | struct stat64 st; |
ce7a5ef4 | 758 | |
4c0fe6fe | 759 | if (stat64 (fname, &st) < 0) |
ce7a5ef4 | 760 | { |
601d2942 | 761 | if (mkdir (fname, 0777) >= 0) |
ce7a5ef4 RM |
762 | { |
763 | fd = -1; | |
764 | errno = EISDIR; | |
765 | } | |
766 | } | |
601d2942 | 767 | else if (!S_ISREG (st.st_mode)) |
ce7a5ef4 RM |
768 | { |
769 | fd = -1; | |
770 | errno = EISDIR; | |
771 | } | |
036cc82f | 772 | } |
601d2942 UD |
773 | |
774 | /* Create the locale file with nlinks == 1; this avoids crashing processes | |
aac0e8c4 UD |
775 | which currently use the locale and damaging files belonging to other |
776 | locales as well. */ | |
601d2942 UD |
777 | if (fd == -2) |
778 | { | |
779 | unlink (fname); | |
780 | fd = creat (fname, 0666); | |
781 | } | |
036cc82f | 782 | |
19bc17a9 RM |
783 | if (fd == -1) |
784 | { | |
785 | int save_err = errno; | |
786 | ||
787 | if (errno == EISDIR) | |
788 | { | |
601d2942 UD |
789 | sprintf (fname, "%1$s%2$s/SYS_%2$s", output_path, category); |
790 | unlink (fname); | |
19bc17a9 RM |
791 | fd = creat (fname, 0666); |
792 | if (fd == -1) | |
793 | save_err = errno; | |
794 | } | |
795 | ||
880f421f | 796 | if (fd == -1) |
19bc17a9 | 797 | { |
880f421f | 798 | if (!be_quiet) |
f2b98f97 UD |
799 | WITH_CUR_LOCALE (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 | { |
880f421f | 822 | if (!be_quiet) |
f2b98f97 UD |
823 | WITH_CUR_LOCALE (error (0, errno, _("\ |
824 | failure while writing data for category `%s'"), category)); | |
19bc17a9 RM |
825 | break; |
826 | } | |
827 | } | |
828 | ||
829 | close (fd); | |
aac0e8c4 UD |
830 | |
831 | /* Compare the file with the locale data files for the same category in | |
832 | other locales, and see if we can reuse it, to save disk space. */ | |
833 | other_paths = siblings (output_path); | |
834 | if (other_paths != NULL) | |
835 | { | |
4c0fe6fe | 836 | struct stat64 fname_stat; |
aac0e8c4 | 837 | |
4c0fe6fe | 838 | if (lstat64 (fname, &fname_stat) >= 0 |
aac0e8c4 UD |
839 | && S_ISREG (fname_stat.st_mode)) |
840 | { | |
841 | const char *fname_tail = fname + strlen (output_path); | |
842 | const char **other_p; | |
843 | int seen_count; | |
844 | ino_t *seen_inodes; | |
845 | ||
846 | seen_count = 0; | |
847 | for (other_p = other_paths; *other_p; other_p++) | |
848 | seen_count++; | |
849 | seen_inodes = (ino_t *) xmalloc (seen_count * sizeof (ino_t)); | |
850 | seen_count = 0; | |
851 | ||
852 | for (other_p = other_paths; *other_p; other_p++) | |
853 | { | |
854 | const char *other_path = *other_p; | |
855 | size_t other_path_len = strlen (other_path); | |
856 | char *other_fname; | |
4c0fe6fe | 857 | struct stat64 other_fname_stat; |
aac0e8c4 UD |
858 | |
859 | other_fname = | |
860 | (char *) xmalloc (other_path_len + strlen (fname_tail) + 1); | |
861 | memcpy (other_fname, other_path, other_path_len); | |
862 | strcpy (other_fname + other_path_len, fname_tail); | |
863 | ||
4c0fe6fe | 864 | if (lstat64 (other_fname, &other_fname_stat) >= 0 |
aac0e8c4 UD |
865 | && S_ISREG (other_fname_stat.st_mode) |
866 | /* Consider only files on the same device. | |
867 | Otherwise hard linking won't work anyway. */ | |
868 | && other_fname_stat.st_dev == fname_stat.st_dev | |
869 | /* Consider only files with the same permissions. | |
870 | Otherwise there are security risks. */ | |
871 | && other_fname_stat.st_uid == fname_stat.st_uid | |
872 | && other_fname_stat.st_gid == fname_stat.st_gid | |
873 | && other_fname_stat.st_mode == fname_stat.st_mode | |
874 | /* Don't compare fname with itself. */ | |
875 | && other_fname_stat.st_ino != fname_stat.st_ino | |
876 | /* Files must have the same size, otherwise they | |
877 | cannot be the same. */ | |
878 | && other_fname_stat.st_size == fname_stat.st_size) | |
879 | { | |
880 | /* Skip this file if we have already read it (under a | |
881 | different name). */ | |
882 | int i; | |
883 | ||
884 | for (i = seen_count - 1; i >= 0; i--) | |
885 | if (seen_inodes[i] == other_fname_stat.st_ino) | |
886 | break; | |
887 | if (i < 0) | |
888 | { | |
889 | /* Now compare fname and other_fname for real. */ | |
890 | blksize_t blocksize; | |
891 | ||
892 | #ifdef _STATBUF_ST_BLKSIZE | |
893 | blocksize = MAX (fname_stat.st_blksize, | |
894 | other_fname_stat.st_blksize); | |
895 | if (blocksize > 8 * 1024) | |
896 | blocksize = 8 * 1024; | |
897 | #else | |
898 | blocksize = 8 * 1024; | |
899 | #endif | |
900 | ||
901 | if (compare_files (fname, other_fname, | |
902 | fname_stat.st_size, blocksize) == 0) | |
903 | { | |
904 | /* Found! other_fname is identical to fname. */ | |
905 | /* Link other_fname to fname. But use a temporary | |
906 | file, in case hard links don't work on the | |
907 | particular filesystem. */ | |
908 | char * tmp_fname = | |
909 | (char *) xmalloc (strlen (fname) + 4 + 1); | |
910 | ||
a7b65cdc | 911 | strcpy (stpcpy (tmp_fname, fname), ".tmp"); |
aac0e8c4 UD |
912 | |
913 | if (link (other_fname, tmp_fname) >= 0) | |
914 | { | |
915 | unlink (fname); | |
916 | if (rename (tmp_fname, fname) < 0) | |
917 | { | |
918 | if (!be_quiet) | |
f2b98f97 UD |
919 | WITH_CUR_LOCALE (error (0, errno, _("\ |
920 | cannot create output file `%s' for category `%s'"), fname, category)); | |
aac0e8c4 UD |
921 | } |
922 | free (tmp_fname); | |
923 | free (other_fname); | |
924 | break; | |
925 | } | |
926 | free (tmp_fname); | |
927 | } | |
928 | ||
929 | /* Don't compare with this file a second time. */ | |
930 | seen_inodes[seen_count++] = other_fname_stat.st_ino; | |
931 | } | |
932 | } | |
933 | free (other_fname); | |
934 | } | |
935 | free (seen_inodes); | |
936 | } | |
937 | } | |
938 | ||
939 | free (fname); | |
19bc17a9 | 940 | } |
25337753 UD |
941 | |
942 | ||
943 | /* General handling of `copy'. */ | |
944 | void | |
945 | handle_copy (struct linereader *ldfile, const struct charmap_t *charmap, | |
946 | const char *repertoire_name, struct localedef_t *result, | |
947 | enum token_t token, int locale, const char *locale_name, | |
948 | int ignore_content) | |
949 | { | |
950 | struct token *now; | |
951 | int warned = 0; | |
952 | ||
953 | now = lr_token (ldfile, charmap, result, NULL, verbose); | |
954 | if (now->tok != tok_string) | |
1d20f7f8 | 955 | lr_error (ldfile, _("expecting string argument for `copy'")); |
25337753 UD |
956 | else if (!ignore_content) |
957 | { | |
958 | if (now->val.str.startmb == NULL) | |
959 | lr_error (ldfile, _("\ | |
960 | locale name should consist only of portable characters")); | |
961 | else | |
962 | { | |
963 | (void) add_to_readlist (locale, now->val.str.startmb, | |
964 | repertoire_name, 1, NULL); | |
965 | result->copy_name[locale] = now->val.str.startmb; | |
966 | } | |
967 | } | |
968 | ||
969 | lr_ignore_rest (ldfile, now->tok == tok_string); | |
970 | ||
971 | /* The rest of the line must be empty and the next keyword must be | |
972 | `END xxx'. */ | |
973 | while ((now = lr_token (ldfile, charmap, result, NULL, verbose))->tok | |
974 | != tok_end && now->tok != tok_eof) | |
975 | { | |
976 | if (warned == 0) | |
977 | { | |
978 | lr_error (ldfile, _("\ | |
979 | no other keyword shall be specified when `copy' is used")); | |
980 | warned = 1; | |
981 | } | |
982 | ||
983 | lr_ignore_rest (ldfile, 0); | |
984 | } | |
985 | ||
986 | if (now->tok != tok_eof) | |
987 | { | |
988 | /* Handle `END xxx'. */ | |
989 | now = lr_token (ldfile, charmap, result, NULL, verbose); | |
990 | ||
991 | if (now->tok != token) | |
992 | lr_error (ldfile, _("\ | |
993 | `%1$s' definition does not end with `END %1$s'"), locale_name); | |
994 | ||
995 | lr_ignore_rest (ldfile, now->tok == token); | |
996 | } | |
997 | else | |
998 | /* When we come here we reached the end of the file. */ | |
999 | lr_error (ldfile, _("%s: premature end of file"), locale_name); | |
1000 | } |