]>
Commit | Line | Data |
---|---|---|
df4ef2ab | 1 | /* Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc. |
e4cf5070 UD |
2 | This file is part of the GNU C Library. |
3 | Contributed by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1995. | |
19bc17a9 | 4 | |
e4cf5070 UD |
5 | The GNU C Library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Library General Public License as | |
7 | published by the Free Software Foundation; either version 2 of the | |
8 | License, or (at your option) any later version. | |
19bc17a9 | 9 | |
e4cf5070 UD |
10 | The GNU C Library is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
13 | Library General Public License for more details. | |
19bc17a9 | 14 | |
e4cf5070 UD |
15 | You should have received a copy of the GNU Library General Public |
16 | License along with the GNU C Library; see the file COPYING.LIB. If not, | |
17 | write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |
18 | Boston, MA 02111-1307, USA. */ | |
19bc17a9 RM |
19 | |
20 | #ifdef HAVE_CONFIG_H | |
21 | # include <config.h> | |
22 | #endif | |
23 | ||
5a97622d | 24 | #include <argp.h> |
19bc17a9 RM |
25 | #include <errno.h> |
26 | #include <fcntl.h> | |
19bc17a9 RM |
27 | #include <libintl.h> |
28 | #include <locale.h> | |
29 | #include <stdio.h> | |
30 | #include <stdlib.h> | |
31 | #include <string.h> | |
32 | #include <unistd.h> | |
33 | #include <sys/mman.h> | |
34 | #include <sys/stat.h> | |
35 | ||
36 | #include "error.h" | |
37 | #include "charset.h" | |
38 | #include "locfile.h" | |
39 | ||
40 | /* Undefine the following line in the production version. */ | |
41 | /* #define NDEBUG 1 */ | |
42 | #include <assert.h> | |
43 | ||
44 | ||
45 | /* List of locale definition files which are used in `copy' instructions. */ | |
46 | struct copy_def_list_t | |
47 | { | |
48 | struct copy_def_list_t *next; | |
49 | ||
50 | const char *name; | |
51 | int mask; | |
52 | ||
53 | struct localedef_t *locale; | |
54 | ||
55 | struct | |
56 | { | |
57 | void *data; | |
58 | size_t len; | |
59 | } binary[6]; | |
60 | }; | |
61 | ||
62 | ||
63 | /* List of copied locales. */ | |
64 | struct copy_def_list_t *copy_list; | |
65 | ||
66 | /* If this is defined be POSIX conform. */ | |
67 | int posix_conformance; | |
68 | ||
19bc17a9 RM |
69 | /* If not zero give a lot more messages. */ |
70 | int verbose; | |
71 | ||
c84142e8 UD |
72 | /* If not zero suppress warnings and information messages. */ |
73 | int be_quiet; | |
19bc17a9 | 74 | |
5a97622d UD |
75 | /* If not zero force output even if warning were issued. */ |
76 | static int force_output; | |
19bc17a9 | 77 | |
5a97622d UD |
78 | /* Name of the character map file. */ |
79 | static const char *charmap_file; | |
80 | ||
81 | /* Name of the locale definition file. */ | |
82 | static const char *input_file; | |
83 | ||
84 | /* Name of the UCS file. */ | |
85 | static const char *ucs_csn; | |
86 | ||
87 | ||
88 | /* Name and version of program. */ | |
89 | static void print_version (FILE *stream, struct argp_state *state); | |
90 | void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version; | |
91 | ||
92 | #define OPT_POSIX 1 | |
93 | #define OPT_QUIET 2 | |
94 | ||
95 | /* Definitions of arguments for argp functions. */ | |
96 | static const struct argp_option options[] = | |
97 | { | |
98 | { NULL, 0, NULL, 0, N_("Input Files:") }, | |
99 | { "charmap", 'f', "FILE", 0, | |
100 | N_("Symbolic character names defined in FILE") }, | |
101 | { "inputfile", 'i', "FILE", 0, N_("Source definitions are found in FILE") }, | |
102 | { "code-set-name", 'u', "NAME", OPTION_HIDDEN, | |
103 | N_("Specify code set for mapping ISO 10646 elements") }, | |
104 | ||
105 | { NULL, 0, NULL, 0, N_("Output control:") }, | |
106 | { "force", 'c', NULL, 0, | |
107 | N_("Create output even if warning messages were issued") }, | |
108 | { "posix", OPT_POSIX, NULL, 0, N_("Be strictly POSIX conform") }, | |
109 | { "quiet", OPT_QUIET, NULL, 0, | |
110 | N_("Suppress warnings and information messages") }, | |
a5a0310d | 111 | { "verbose", 'v', NULL, 0, N_("Print more messages") }, |
5a97622d UD |
112 | { NULL, 0, NULL, 0, NULL } |
113 | }; | |
114 | ||
115 | /* Short description of program. */ | |
116 | static const char doc[] = N_("Compile locale specification"); | |
117 | ||
118 | /* Strings for arguments in help texts. */ | |
119 | static const char args_doc[] = N_("NAME"); | |
120 | ||
121 | /* Prototype for option handler. */ | |
122 | static error_t parse_opt (int key, char *arg, struct argp_state *state); | |
123 | ||
124 | /* Function to print some extra text in the help message. */ | |
125 | static char *more_help (int key, const char *text, void *input); | |
126 | ||
127 | /* Data structure to communicate with argp functions. */ | |
128 | static struct argp argp = | |
19bc17a9 | 129 | { |
5a97622d | 130 | options, parse_opt, args_doc, doc, NULL, more_help |
19bc17a9 RM |
131 | }; |
132 | ||
133 | ||
134 | /* Prototypes for global functions. */ | |
135 | void *xmalloc (size_t __n); | |
136 | ||
137 | /* Prototypes for local functions. */ | |
19bc17a9 | 138 | static void error_print (void); |
7a12c6bb | 139 | static const char *construct_output_path (char *path); |
900bec85 | 140 | static const char *normalize_codeset (const char *codeset, size_t name_len); |
19bc17a9 RM |
141 | |
142 | ||
143 | int | |
144 | main (int argc, char *argv[]) | |
145 | { | |
19bc17a9 RM |
146 | const char *output_path; |
147 | int cannot_write_why; | |
148 | struct charset_t *charset; | |
149 | struct localedef_t *localedef; | |
150 | struct copy_def_list_t *act_add_locdef; | |
2f6d1f1b | 151 | int remaining; |
19bc17a9 | 152 | |
6d52618b | 153 | /* Set initial values for global variables. */ |
19bc17a9 RM |
154 | copy_list = NULL; |
155 | posix_conformance = getenv ("POSIXLY_CORRECT") != NULL; | |
19bc17a9 | 156 | error_print_progname = error_print; |
19bc17a9 RM |
157 | |
158 | /* Set locale. Do not set LC_ALL because the other categories must | |
6d52618b | 159 | not be affected (according to POSIX.2). */ |
19bc17a9 RM |
160 | setlocale (LC_MESSAGES, ""); |
161 | setlocale (LC_CTYPE, ""); | |
162 | ||
163 | /* Initialize the message catalog. */ | |
19bc17a9 | 164 | textdomain (_libc_intl_domainname); |
19bc17a9 | 165 | |
5a97622d | 166 | /* Parse and process arguments. */ |
4cca6b86 | 167 | argp_err_exit_status = 4; |
2f6d1f1b | 168 | argp_parse (&argp, argc, argv, 0, &remaining, NULL); |
c84142e8 | 169 | |
19bc17a9 RM |
170 | /* POSIX.2 requires to be verbose about missing characters in the |
171 | character map. */ | |
172 | verbose |= posix_conformance; | |
173 | ||
2f6d1f1b | 174 | if (argc - remaining != 1) |
19bc17a9 | 175 | { |
5a97622d | 176 | /* We need exactly one non-option parameter. */ |
4cca6b86 | 177 | argp_help (&argp, stdout, ARGP_HELP_SEE | ARGP_HELP_EXIT_ERR, |
5a97622d | 178 | program_invocation_short_name); |
5a97622d | 179 | exit (4); |
19bc17a9 RM |
180 | } |
181 | ||
19bc17a9 RM |
182 | /* The parameter describes the output path of the constructed files. |
183 | If the described files cannot be written return a NULL pointer. */ | |
2f6d1f1b | 184 | output_path = construct_output_path (argv[remaining]); |
19bc17a9 RM |
185 | cannot_write_why = errno; |
186 | ||
187 | /* Now that the parameters are processed we have to reset the local | |
188 | ctype locale. (P1003.2 4.35.5.2) */ | |
189 | setlocale (LC_CTYPE, "POSIX"); | |
190 | ||
191 | /* Look whether the system really allows locale definitions. POSIX | |
192 | defines error code 3 for this situation so I think it must be | |
193 | a fatal error (see P1003.2 4.35.8). */ | |
194 | if (sysconf (_SC_2_LOCALEDEF) < 0) | |
195 | error (3, 0, _("FATAL: system does not define `_POSIX2_LOCALEDEF'")); | |
196 | ||
197 | /* Process charmap file. */ | |
198 | charset = charmap_read (charmap_file); | |
199 | ||
200 | /* Now read the locale file. */ | |
201 | localedef = locfile_read (input_file, charset); | |
202 | if (localedef->failed != 0) | |
203 | error (4, errno, _("cannot open locale definition file `%s'"), input_file); | |
204 | ||
205 | /* Perhaps we saw some `copy' instructions. Process the given list. | |
206 | We use a very simple algorithm: we look up the list from the | |
207 | beginning every time. */ | |
208 | do | |
209 | { | |
210 | int cat; | |
211 | ||
212 | for (act_add_locdef = copy_list; act_add_locdef != NULL; | |
213 | act_add_locdef = act_add_locdef->next) | |
214 | { | |
75cd5204 | 215 | for (cat = LC_CTYPE; cat <= LC_MESSAGES; ++cat) |
19bc17a9 RM |
216 | if ((act_add_locdef->mask & (1 << cat)) != 0) |
217 | { | |
218 | act_add_locdef->mask &= ~(1 << cat); | |
219 | break; | |
220 | } | |
221 | if (cat <= LC_MESSAGES) | |
222 | break; | |
223 | } | |
224 | ||
225 | if (act_add_locdef != NULL) | |
226 | { | |
227 | int avail = 0; | |
228 | ||
229 | if (act_add_locdef->locale == NULL) | |
230 | act_add_locdef->locale = locfile_read (act_add_locdef->name, | |
231 | charset); | |
232 | ||
233 | if (! act_add_locdef->locale->failed) | |
234 | { | |
235 | avail = act_add_locdef->locale->categories[cat].generic != NULL; | |
236 | if (avail) | |
55c14926 UD |
237 | { |
238 | localedef->categories[cat].generic | |
239 | = act_add_locdef->locale->categories[cat].generic; | |
240 | localedef->avail |= 1 << cat; | |
241 | } | |
19bc17a9 RM |
242 | } |
243 | ||
244 | if (! avail) | |
245 | { | |
f166d865 UD |
246 | static const char *locale_names[] = |
247 | { | |
248 | "LC_COLLATE", "LC_CTYPE", "LC_MONETARY", | |
249 | "LC_NUMERIC", "LC_TIME", "LC_MESSAGES" | |
250 | }; | |
19bc17a9 RM |
251 | char *fname; |
252 | int fd; | |
253 | struct stat st; | |
254 | ||
f166d865 | 255 | asprintf (&fname, LOCALEDIR "/%s/%s", act_add_locdef->name, |
19bc17a9 RM |
256 | locale_names[cat]); |
257 | fd = open (fname, O_RDONLY); | |
258 | if (fd == -1) | |
259 | { | |
260 | free (fname); | |
261 | ||
f166d865 | 262 | asprintf (&fname, LOCALEDIR "/%s/%s/SYS_%s", |
19bc17a9 RM |
263 | act_add_locdef->name, locale_names[cat], |
264 | locale_names[cat]); | |
265 | ||
266 | fd = open (fname, O_RDONLY); | |
267 | if (fd == -1) | |
268 | error (5, 0, _("\ | |
269 | locale file `%s', used in `copy' statement, not found"), | |
270 | act_add_locdef->name); | |
271 | } | |
272 | ||
273 | if (fstat (fd, &st) < 0) | |
274 | error (5, errno, _("\ | |
275 | cannot `stat' locale file `%s'"), | |
276 | fname); | |
277 | ||
278 | localedef->len[cat] = st.st_size; | |
279 | localedef->categories[cat].generic | |
280 | = mmap (NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); | |
281 | ||
0413b54c | 282 | if (localedef->categories[cat].generic == MAP_FAILED) |
19bc17a9 RM |
283 | { |
284 | size_t left = st.st_size; | |
285 | void *read_ptr; | |
286 | ||
287 | localedef->categories[cat].generic | |
288 | = xmalloc (st.st_size); | |
289 | read_ptr = localedef->categories[cat].generic; | |
290 | ||
291 | do | |
292 | { | |
293 | long int n; | |
294 | n = read (fd, read_ptr, left); | |
0d204b0a | 295 | if (n == -1) |
19bc17a9 RM |
296 | error (5, errno, _("cannot read locale file `%s'"), |
297 | fname); | |
298 | read_ptr += n; | |
299 | left -= n; | |
300 | } | |
301 | while (left > 0); | |
302 | } | |
303 | ||
304 | close (fd); | |
305 | free (fname); | |
306 | ||
307 | localedef->binary |= 1 << cat; | |
308 | } | |
309 | } | |
310 | } | |
311 | while (act_add_locdef != NULL); | |
312 | ||
313 | /* Check the categories we processed in source form. */ | |
314 | check_all_categories (localedef, charset); | |
315 | ||
316 | /* We are now able to write the data files. If warning were given we | |
317 | do it only if it is explicitly requested (--force). */ | |
318 | if (error_message_count == 0 || force_output != 0) | |
319 | { | |
320 | if (cannot_write_why != 0) | |
321 | error (4, cannot_write_why, _("cannot write output files to `%s'"), | |
322 | output_path); | |
323 | else | |
75cd5204 | 324 | write_all_categories (localedef, charset, output_path); |
19bc17a9 RM |
325 | } |
326 | else | |
327 | error (4, 0, _("no output file produced because warning were issued")); | |
328 | ||
329 | /* This exit status is prescribed by POSIX.2 4.35.7. */ | |
330 | exit (error_message_count != 0); | |
331 | } | |
332 | ||
333 | ||
5a97622d UD |
334 | /* Handle program arguments. */ |
335 | static error_t | |
336 | parse_opt (int key, char *arg, struct argp_state *state) | |
337 | { | |
338 | switch (key) | |
339 | { | |
340 | case OPT_QUIET: | |
341 | be_quiet = 1; | |
342 | break; | |
343 | case OPT_POSIX: | |
344 | posix_conformance = 1; | |
345 | break; | |
346 | case 'c': | |
347 | force_output = 1; | |
348 | break; | |
349 | case 'f': | |
350 | charmap_file = arg; | |
351 | break; | |
352 | case 'i': | |
353 | input_file = arg; | |
354 | break; | |
355 | case 'u': | |
356 | ucs_csn = arg; | |
357 | break; | |
358 | case 'v': | |
359 | verbose = 1; | |
360 | break; | |
361 | default: | |
362 | return ARGP_ERR_UNKNOWN; | |
363 | } | |
364 | return 0; | |
365 | } | |
366 | ||
367 | ||
368 | static char * | |
369 | more_help (int key, const char *text, void *input) | |
370 | { | |
371 | char *cp; | |
372 | ||
373 | switch (key) | |
374 | { | |
375 | case ARGP_KEY_HELP_EXTRA: | |
376 | /* We print some extra information. */ | |
377 | asprintf (&cp, gettext ("\ | |
378 | System's directory for character maps: %s\n\ | |
379 | locale files : %s\n\ | |
380 | %s"), | |
381 | CHARMAP_PATH, LOCALE_PATH, gettext ("\ | |
f2ea0f5b | 382 | Report bugs using the `glibcbug' script to <bugs@gnu.org>.\n")); |
5a97622d UD |
383 | return cp; |
384 | default: | |
385 | break; | |
386 | } | |
387 | return (char *) text; | |
388 | } | |
389 | ||
390 | /* Print the version information. */ | |
391 | static void | |
392 | print_version (FILE *stream, struct argp_state *state) | |
393 | { | |
394 | fprintf (stream, "localedef (GNU %s) %s\n", PACKAGE, VERSION); | |
395 | fprintf (stream, gettext ("\ | |
396 | Copyright (C) %s Free Software Foundation, Inc.\n\ | |
397 | This is free software; see the source for copying conditions. There is NO\n\ | |
398 | warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\ | |
399 | "), "1995, 1996, 1997"); | |
400 | fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper"); | |
401 | } | |
402 | ||
403 | ||
19bc17a9 RM |
404 | void |
405 | def_to_process (const char *name, int category) | |
406 | { | |
407 | struct copy_def_list_t *new, **rp; | |
408 | ||
409 | for (rp = ©_list; *rp != NULL; rp = &(*rp)->next) | |
410 | if (strcmp (name, (*rp)->name) == 0) | |
411 | break; | |
412 | ||
413 | if (*rp == NULL) | |
414 | { | |
415 | size_t cnt; | |
416 | ||
417 | *rp = (struct copy_def_list_t *) xmalloc (sizeof (**rp)); | |
418 | ||
419 | (*rp)->next = NULL; | |
420 | (*rp)->name = name; | |
421 | (*rp)->mask = 0; | |
422 | (*rp)->locale = NULL; | |
423 | ||
424 | for (cnt = 0; cnt < 6; ++cnt) | |
425 | { | |
426 | (*rp)->binary[cnt].data = NULL; | |
427 | (*rp)->binary[cnt].len = 0; | |
428 | } | |
429 | } | |
430 | new = *rp; | |
431 | ||
432 | if ((new->mask & category) != 0) | |
433 | /* We already have the information. This cannot happen. */ | |
434 | error (5, 0, _("\ | |
435 | category data requested more than once: should not happen")); | |
436 | ||
437 | new->mask |= category; | |
438 | } | |
439 | ||
440 | ||
19bc17a9 RM |
441 | /* The address of this function will be assigned to the hook in the error |
442 | functions. */ | |
443 | static void | |
444 | error_print () | |
445 | { | |
446 | /* We don't want the program name to be printed in messages. Emacs' | |
447 | compile.el does not like this. */ | |
448 | } | |
449 | ||
450 | ||
451 | /* The parameter to localedef describes the output path. If it does | |
6d52618b | 452 | contain a '/' character it is a relative path. Otherwise it names the |
19bc17a9 RM |
453 | locale this definition is for. */ |
454 | static const char * | |
7a12c6bb | 455 | construct_output_path (char *path) |
19bc17a9 | 456 | { |
c4029823 | 457 | const char *normal = NULL; |
19bc17a9 RM |
458 | char *result; |
459 | ||
460 | if (strchr (path, '/') == NULL) | |
461 | { | |
7a12c6bb RM |
462 | /* This is a system path. First examine whether the locale name |
463 | contains a reference to the codeset. This should be | |
464 | normalized. */ | |
465 | char *startp, *endp; | |
466 | ||
467 | startp = path; | |
468 | /* We must be prepared for finding a CEN name or a location of | |
469 | the introducing `.' where it is not possible anymore. */ | |
470 | while (*startp != '\0' && *startp != '@' && *startp != '.' | |
471 | && *startp != '+' && *startp != ',') | |
472 | ++startp; | |
473 | if (*startp == '.') | |
474 | { | |
475 | /* We found a codeset specification. Now find the end. */ | |
476 | endp = ++startp; | |
477 | while (*endp != '\0' && *endp != '@') | |
478 | ++endp; | |
479 | ||
480 | if (endp > startp) | |
900bec85 | 481 | normal = normalize_codeset (startp, endp - startp); |
7a12c6bb | 482 | } |
c4029823 UD |
483 | else |
484 | /* This is to keep gcc quiet. */ | |
485 | endp = NULL; | |
19bc17a9 | 486 | |
7a12c6bb RM |
487 | /* We put an additional '\0' at the end of the string because at |
488 | the end of the function we need another byte for the trailing | |
489 | '/'. */ | |
490 | if (normal == NULL) | |
e4cf5070 | 491 | asprintf (&result, "%s/%s%c", LOCALEDIR, path, '\0'); |
7a12c6bb | 492 | else |
e4cf5070 | 493 | asprintf (&result, "%s/%.*s%s%s%c", LOCALEDIR, startp - path, path, |
ba1ffaa1 | 494 | normal, endp, '\0'); |
19bc17a9 RM |
495 | } |
496 | else | |
497 | { | |
7a12c6bb RM |
498 | /* This is a user path. Please note the additional byte in the |
499 | memory allocation. */ | |
19bc17a9 | 500 | result = xmalloc (strlen (path) + 2); |
7a12c6bb | 501 | strcpy (result, path); |
19bc17a9 RM |
502 | } |
503 | ||
504 | errno = 0; | |
505 | ||
506 | if (euidaccess (result, W_OK) == -1) | |
507 | /* Perhaps the directory does not exist now. Try to create it. */ | |
508 | if (errno == ENOENT) | |
509 | { | |
510 | errno = 0; | |
511 | mkdir (result, 0777); | |
512 | } | |
513 | ||
514 | strcat (result, "/"); | |
515 | ||
516 | return result; | |
517 | } | |
900bec85 UD |
518 | |
519 | /* Normalize codeset name. There is no standard for the codeset | |
520 | names. Normalization allows the user to use any of the common | |
521 | names. */ | |
522 | static const char * | |
523 | normalize_codeset (codeset, name_len) | |
524 | const char *codeset; | |
525 | size_t name_len; | |
526 | { | |
527 | int len = 0; | |
528 | int only_digit = 1; | |
529 | char *retval; | |
530 | char *wp; | |
531 | size_t cnt; | |
532 | ||
533 | for (cnt = 0; cnt < name_len; ++cnt) | |
534 | if (isalnum (codeset[cnt])) | |
535 | { | |
536 | ++len; | |
537 | ||
538 | if (isalpha (codeset[cnt])) | |
539 | only_digit = 0; | |
540 | } | |
541 | ||
542 | retval = (char *) malloc ((only_digit ? 3 : 0) + len + 1); | |
543 | ||
544 | if (retval != NULL) | |
545 | { | |
546 | if (only_digit) | |
547 | wp = stpcpy (retval, "iso"); | |
548 | else | |
549 | wp = retval; | |
550 | ||
551 | for (cnt = 0; cnt < name_len; ++cnt) | |
552 | if (isalpha (codeset[cnt])) | |
553 | *wp++ = tolower (codeset[cnt]); | |
554 | else if (isdigit (codeset[cnt])) | |
555 | *wp++ = codeset[cnt]; | |
556 | ||
557 | *wp = '\0'; | |
558 | } | |
559 | ||
560 | return (const char *) retval; | |
561 | } |