1/* Copyright (C) 1995-2019 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3 Contributed by Ulrich Drepper <drepper@cygnus.com>, 1995.
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 <argp.h>
23#include <errno.h>
24#include <fcntl.h>
25#include <libintl.h>
26#include <locale.h>
27#include <stdbool.h>
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31#include <unistd.h>
32#include <error.h>
33#include <sys/mman.h>
34#include <sys/stat.h>
35#include <ctype.h>
36
37#include "localedef.h"
38#include "charmap.h"
39#include "locfile.h"
40
41/* Undefine the following line in the production version. */
42/* #define NDEBUG 1 */
43#include <assert.h>
44
45
46/* List of copied locales. */
47struct copy_def_list_t *copy_list;
48
49/* If this is defined be POSIX conform. */
50int posix_conformance;
51
52/* If not zero force output even if warning were issued. */
53static int force_output;
54
55/* Prefix for output files. */
56const char *output_prefix;
57
58/* Name of the character map file. */
59static const char *charmap_file;
60
61/* Name of the locale definition file. */
62static const char *input_file;
63
64/* Name of the repertoire map file. */
65const char *repertoire_global;
66
67/* Name of the locale.alias file. */
68const char *alias_file;
69
70/* List of all locales. */
71static struct localedef_t *locales;
72
73/* If true don't add locale data to archive. */
74bool no_archive;
75
76/* If true add named locales to archive. */
77static bool add_to_archive;
78
79/* If true delete named locales from archive. */
80static bool delete_from_archive;
81
82/* If true replace archive content when adding. */
83static bool replace_archive;
84
85/* If true list archive content. */
86static bool list_archive;
87
88/* If true create hard links to other locales (default). */
89bool hard_links = true;
90
91/* Maximum number of retries when opening the locale archive. */
92int max_locarchive_open_retry = 10;
93
94
95/* Name and version of program. */
96static void print_version (FILE *stream, struct argp_state *state);
97void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version;
98
99#define OPT_POSIX 301
100#define OPT_QUIET 302
101#define OPT_PREFIX 304
102#define OPT_NO_ARCHIVE 305
103#define OPT_ADD_TO_ARCHIVE 306
104#define OPT_REPLACE 307
105#define OPT_DELETE_FROM_ARCHIVE 308
106#define OPT_LIST_ARCHIVE 309
107#define OPT_LITTLE_ENDIAN 400
108#define OPT_BIG_ENDIAN 401
109#define OPT_NO_WARN 402
110#define OPT_WARN 403
111#define OPT_NO_HARD_LINKS 404
112
113/* Definitions of arguments for argp functions. */
114static const struct argp_option options[] =
115{
116 { NULL, 0, NULL, 0, N_("Input Files:") },
117 { "charmap", 'f', N_("FILE"), 0,
118 N_("Symbolic character names defined in FILE") },
119 { "inputfile", 'i', N_("FILE"), 0,
120 N_("Source definitions are found in FILE") },
121 { "repertoire-map", 'u', N_("FILE"), 0,
122 N_("FILE contains mapping from symbolic names to UCS4 values") },
123
124 { NULL, 0, NULL, 0, N_("Output control:") },
125 { "force", 'c', NULL, 0,
126 N_("Create output even if warning messages were issued") },
127 { "no-hard-links", OPT_NO_HARD_LINKS, NULL, 0,
128 N_("Do not create hard links between installed locales") },
129 { "prefix", OPT_PREFIX, N_("PATH"), 0, N_("Optional output file prefix") },
130 { "posix", OPT_POSIX, NULL, 0, N_("Strictly conform to POSIX") },
131 { "quiet", OPT_QUIET, NULL, 0,
132 N_("Suppress warnings and information messages") },
133 { "verbose", 'v', NULL, 0, N_("Print more messages") },
134 { "no-warnings", OPT_NO_WARN, N_("<warnings>"), 0,
135 N_("Comma-separated list of warnings to disable; "
136 "supported warnings are: ascii, intcurrsym") },
137 { "warnings", OPT_WARN, N_("<warnings>"), 0,
138 N_("Comma-separated list of warnings to enable; "
139 "supported warnings are: ascii, intcurrsym") },
140
141 { NULL, 0, NULL, 0, N_("Archive control:") },
142 { "no-archive", OPT_NO_ARCHIVE, NULL, 0,
143 N_("Don't add new data to archive") },
144 { "add-to-archive", OPT_ADD_TO_ARCHIVE, NULL, 0,
145 N_("Add locales named by parameters to archive") },
146 { "replace", OPT_REPLACE, NULL, 0, N_("Replace existing archive content") },
147 { "delete-from-archive", OPT_DELETE_FROM_ARCHIVE, NULL, 0,
148 N_("Remove locales named by parameters from archive") },
149 { "list-archive", OPT_LIST_ARCHIVE, NULL, 0, N_("List content of archive") },
150 { "alias-file", 'A', N_("FILE"), 0,
151 N_("locale.alias file to consult when making archive")},
152 { "little-endian", OPT_LITTLE_ENDIAN, NULL, 0,
153 N_("Generate little-endian output") },
154 { "big-endian", OPT_BIG_ENDIAN, NULL, 0,
155 N_("Generate big-endian output") },
156 { NULL, 0, NULL, 0, NULL }
157};
158
159/* Short description of program. */
160static const char doc[] = N_("Compile locale specification");
161
162/* Strings for arguments in help texts. */
163static const char args_doc[] = N_("\
164NAME\n\
165[--add-to-archive|--delete-from-archive] FILE...\n\
166--list-archive [FILE]");
167
168/* Prototype for option handler. */
169static error_t parse_opt (int key, char *arg, struct argp_state *state);
170
171/* Function to print some extra text in the help message. */
172static char *more_help (int key, const char *text, void *input);
173
174/* Data structure to communicate with argp functions. */
175static struct argp argp =
176{
177 options, parse_opt, args_doc, doc, NULL, more_help
178};
179
180
181/* Prototypes for local functions. */
182static void error_print (void);
183static const char *construct_output_path (char *path);
184static const char *normalize_codeset (const char *codeset, size_t name_len);
185
186
187int
188main (int argc, char *argv[])
189{
190 const char *output_path;
191 int cannot_write_why;
192 struct charmap_t *charmap;
193 struct localedef_t global;
194 int remaining;
195
196 /* Set initial values for global variables. */
197 copy_list = NULL;
198 posix_conformance = getenv ("POSIXLY_CORRECT") != NULL;
199 error_print_progname = error_print;
200
201 /* Set locale. Do not set LC_ALL because the other categories must
202 not be affected (according to POSIX.2). */
203 setlocale (LC_MESSAGES, "");
204 setlocale (LC_CTYPE, "");
205
206 /* Initialize the message catalog. */
207 textdomain (_libc_intl_domainname);
208
209 /* Parse and process arguments. */
210 argp_err_exit_status = 4;
211 argp_parse (&argp, argc, argv, 0, &remaining, NULL);
212
213 /* Handle a few special cases. */
214 if (list_archive)
215 show_archive_content (remaining > 1 ? argv[remaining] : NULL, verbose);
216 if (add_to_archive)
217 return add_locales_to_archive (argc - remaining, &argv[remaining],
218 replace_archive);
219 if (delete_from_archive)
220 return delete_locales_from_archive (argc - remaining, &argv[remaining]);
221
222 /* POSIX.2 requires to be verbose about missing characters in the
223 character map. */
224 verbose |= posix_conformance;
225
226 if (argc - remaining != 1)
227 {
228 /* We need exactly one non-option parameter. */
229 argp_help (&argp, stdout, ARGP_HELP_SEE | ARGP_HELP_EXIT_ERR,
230 program_invocation_short_name);
231 exit (4);
232 }
233
234 /* The parameter describes the output path of the constructed files.
235 If the described files cannot be written return a NULL pointer. */
236 output_path = construct_output_path (argv[remaining]);
237 if (output_path == NULL && ! no_archive)
238 error (4, errno, _("cannot create directory for output files"));
239 cannot_write_why = errno;
240
241 /* Now that the parameters are processed we have to reset the local
242 ctype locale. (P1003.2 4.35.5.2) */
243 setlocale (LC_CTYPE, "POSIX");
244
245 /* Look whether the system really allows locale definitions. POSIX
246 defines error code 3 for this situation so I think it must be
247 a fatal error (see P1003.2 4.35.8). */
248 if (sysconf (_SC_2_LOCALEDEF) < 0)
249 record_error (3, 0, _("\
250FATAL: system does not define `_POSIX2_LOCALEDEF'"));
251
252 /* Process charmap file. */
253 charmap = charmap_read (charmap_file, verbose, 1, be_quiet, 1);
254
255 /* Add the first entry in the locale list. */
256 memset (&global, '\0', sizeof (struct localedef_t));
257 global.name = input_file ?: "/dev/stdin";
258 global.needed = ALL_LOCALES;
259 locales = &global;
260
261 /* Now read the locale file. */
262 if (locfile_read (&global, charmap) != 0)
263 record_error (4, errno, _("\
264cannot open locale definition file `%s'"), input_file);
265
266 /* Perhaps we saw some `copy' instructions. */
267 while (1)
268 {
269 struct localedef_t *runp = locales;
270
271 while (runp != NULL && (runp->needed & runp->avail) == runp->needed)
272 runp = runp->next;
273
274 if (runp == NULL)
275 /* Everything read. */
276 break;
277
278 if (locfile_read (runp, charmap) != 0)
279 record_error (4, errno, _("\
280cannot open locale definition file `%s'"), runp->name);
281 }
282
283 /* Check the categories we processed in source form. */
284 check_all_categories (locales, charmap);
285
286 /* What we do next depends on the number of errors and warnings we
287 have generated in processing the input files.
288
289 * No errors: Write the output file.
290
291 * Some warnings: Write the output file and exit with status 1 to
292 indicate there may be problems using the output file e.g. missing
293 data that makes it difficult to use
294
295 * Errors: We don't write the output file and we exit with status 4
296 to indicate no output files were written.
297
298 The use of -c|--force writes the output file even if errors were
299 seen. */
300 if (recorded_error_count == 0 || force_output != 0)
301 {
302 if (cannot_write_why != 0)
303 record_error (4, cannot_write_why, _("\
304cannot write output files to `%s'"), output_path ? : argv[remaining]);
305 else
306 write_all_categories (locales, charmap, argv[remaining], output_path);
307 }
308 else
309 record_error (4, 0, _("\
310no output file produced because errors were issued"));
311
312 /* This exit status is prescribed by POSIX.2 4.35.7. */
313 exit (recorded_warning_count != 0);
314}
315
316/* Search warnings for matching warnings and if found enable those
317 warnings if ENABLED is true, otherwise disable the warnings. */
318static void
319set_warnings (char *warnings, bool enabled)
320{
321 char *tok = warnings;
322 char *copy = (char *) malloc (strlen (warnings) + 1);
323 char *save = copy;
324
325 /* As we make a copy of the warnings list we remove all spaces from
326 the warnings list to make the processing a more robust. We don't
327 support spaces in a warning name. */
328 do
329 {
330 while (isspace (*tok) != 0)
331 tok++;
332 }
333 while ((*save++ = *tok++) != '\0');
334
335 warnings = copy;
336
337 /* Tokenize the input list of warnings to set, compare them to
338 known warnings, and set the warning. We purposely ignore unknown
339 warnings, and are thus forward compatible, users can attempt to
340 disable whaterver new warnings they know about, but we will only
341 disable those *we* known about. */
342 while ((tok = strtok_r (warnings, ",", &save)) != NULL)
343 {
344 warnings = NULL;
345 if (strcmp (tok, "ascii") == 0)
346 warn_ascii = enabled;
347 else if (strcmp (tok, "intcurrsym") == 0)
348 warn_int_curr_symbol = enabled;
349 }
350
351 free (copy);
352}
353
354/* Handle program arguments. */
355static error_t
356parse_opt (int key, char *arg, struct argp_state *state)
357{
358 switch (key)
359 {
360 case OPT_QUIET:
361 be_quiet = 1;
362 break;
363 case OPT_POSIX:
364 posix_conformance = 1;
365 break;
366 case OPT_PREFIX:
367 output_prefix = arg;
368 break;
369 case OPT_NO_ARCHIVE:
370 no_archive = true;
371 break;
372 case OPT_ADD_TO_ARCHIVE:
373 add_to_archive = true;
374 break;
375 case OPT_REPLACE:
376 replace_archive = true;
377 break;
378 case OPT_DELETE_FROM_ARCHIVE:
379 delete_from_archive = true;
380 break;
381 case OPT_LIST_ARCHIVE:
382 list_archive = true;
383 break;
384 case OPT_LITTLE_ENDIAN:
385 set_big_endian (false);
386 break;
387 case OPT_BIG_ENDIAN:
388 set_big_endian (true);
389 break;
390 case OPT_NO_WARN:
391 /* Disable the warnings. */
392 set_warnings (arg, false);
393 break;
394 case OPT_WARN:
395 /* Enable the warnings. */
396 set_warnings (arg, true);
397 break;
398 case OPT_NO_HARD_LINKS:
399 /* Do not hard link to other locales. */
400 hard_links = false;
401 break;
402 case 'c':
403 force_output = 1;
404 break;
405 case 'f':
406 charmap_file = arg;
407 break;
408 case 'A':
409 alias_file = arg;
410 break;
411 case 'i':
412 input_file = arg;
413 break;
414 case 'u':
415 repertoire_global = arg;
416 break;
417 case 'v':
418 verbose = 1;
419 break;
420 default:
421 return ARGP_ERR_UNKNOWN;
422 }
423 return 0;
424}
425
426
427static char *
428more_help (int key, const char *text, void *input)
429{
430 char *cp;
431 char *tp;
432
433 switch (key)
434 {
435 case ARGP_KEY_HELP_EXTRA:
436 /* We print some extra information. */
437 if (asprintf (&tp, gettext ("\
438For bug reporting instructions, please see:\n\
439%s.\n"), REPORT_BUGS_TO) < 0)
440 return NULL;
441 if (asprintf (&cp, gettext ("\
442System's directory for character maps : %s\n\
443 repertoire maps: %s\n\
444 locale path : %s\n\
445%s"),
446 CHARMAP_PATH, REPERTOIREMAP_PATH, LOCALE_PATH, tp) < 0)
447 {
448 free (tp);
449 return NULL;
450 }
451 return cp;
452 default:
453 break;
454 }
455 return (char *) text;
456}
457
458/* Print the version information. */
459static void
460print_version (FILE *stream, struct argp_state *state)
461{
462 fprintf (stream, "localedef %s%s\n", PKGVERSION, VERSION);
463 fprintf (stream, gettext ("\
464Copyright (C) %s Free Software Foundation, Inc.\n\
465This is free software; see the source for copying conditions. There is NO\n\
466warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
467"), "2019");
468 fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper");
469}
470
471
472/* The address of this function will be assigned to the hook in the error
473 functions. */
474static void
475error_print (void)
476{
477}
478
479
480/* The parameter to localedef describes the output path. If it does
481 contain a '/' character it is a relative path. Otherwise it names the
482 locale this definition is for. */
483static const char *
484construct_output_path (char *path)
485{
486 const char *normal = NULL;
487 char *result;
488 char *endp;
489
490 if (strchr (path, '/') == NULL)
491 {
492 /* This is a system path. First examine whether the locale name
493 contains a reference to the codeset. This should be
494 normalized. */
495 char *startp;
496
497 startp = path;
498 /* We must be prepared for finding a CEN name or a location of
499 the introducing `.' where it is not possible anymore. */
500 while (*startp != '\0' && *startp != '@' && *startp != '.')
501 ++startp;
502 if (*startp == '.')
503 {
504 /* We found a codeset specification. Now find the end. */
505 endp = ++startp;
506 while (*endp != '\0' && *endp != '@')
507 ++endp;
508
509 if (endp > startp)
510 normal = normalize_codeset (startp, endp - startp);
511 }
512 else
513 /* This is to keep gcc quiet. */
514 endp = NULL;
515
516 /* We put an additional '\0' at the end of the string because at
517 the end of the function we need another byte for the trailing
518 '/'. */
519 ssize_t n;
520 if (normal == NULL)
521 n = asprintf (&result, "%s%s/%s%c", output_prefix ?: "",
522 COMPLOCALEDIR, path, '\0');
523 else
524 n = asprintf (&result, "%s%s/%.*s%s%s%c",
525 output_prefix ?: "", COMPLOCALEDIR,
526 (int) (startp - path), path, normal, endp, '\0');
527
528 if (n < 0)
529 return NULL;
530
531 endp = result + n - 1;
532 }
533 else
534 {
535 /* This is a user path. Please note the additional byte in the
536 memory allocation. */
537 size_t len = strlen (path) + 1;
538 result = xmalloc (len + 1);
539 endp = mempcpy (result, path, len) - 1;
540
541 /* If the user specified an output path we cannot add the output
542 to the archive. */
543 no_archive = true;
544 }
545
546 errno = 0;
547
548 if (no_archive && euidaccess (result, W_OK) == -1)
549 /* Perhaps the directory does not exist now. Try to create it. */
550 if (errno == ENOENT)
551 {
552 errno = 0;
553 if (mkdir (result, 0777) < 0)
554 return NULL;
555 }
556
557 *endp++ = '/';
558 *endp = '\0';
559
560 return result;
561}
562
563
564/* Normalize codeset name. There is no standard for the codeset
565 names. Normalization allows the user to use any of the common
566 names. */
567static const char *
568normalize_codeset (const char *codeset, size_t name_len)
569{
570 int len = 0;
571 int only_digit = 1;
572 char *retval;
573 char *wp;
574 size_t cnt;
575
576 for (cnt = 0; cnt < name_len; ++cnt)
577 if (isalnum (codeset[cnt]))
578 {
579 ++len;
580
581 if (isalpha (codeset[cnt]))
582 only_digit = 0;
583 }
584
585 retval = (char *) malloc ((only_digit ? 3 : 0) + len + 1);
586
587 if (retval != NULL)
588 {
589 if (only_digit)
590 wp = stpcpy (retval, "iso");
591 else
592 wp = retval;
593
594 for (cnt = 0; cnt < name_len; ++cnt)
595 if (isalpha (codeset[cnt]))
596 *wp++ = tolower (codeset[cnt]);
597 else if (isdigit (codeset[cnt]))
598 *wp++ = codeset[cnt];
599
600 *wp = '\0';
601 }
602
603 return (const char *) retval;
604}
605
606
607struct localedef_t *
608add_to_readlist (int category, const char *name, const char *repertoire_name,
609 int generate, struct localedef_t *copy_locale)
610{
611 struct localedef_t *runp = locales;
612
613 while (runp != NULL && strcmp (name, runp->name) != 0)
614 runp = runp->next;
615
616 if (runp == NULL)
617 {
618 /* Add a new entry at the end. */
619 struct localedef_t *newp;
620
621 assert (generate == 1);
622
623 newp = xcalloc (1, sizeof (struct localedef_t));
624 newp->name = name;
625 newp->repertoire_name = repertoire_name;
626
627 if (locales == NULL)
628 runp = locales = newp;
629 else
630 {
631 runp = locales;
632 while (runp->next != NULL)
633 runp = runp->next;
634 runp = runp->next = newp;
635 }
636 }
637
638 if (generate
639 && (runp->needed & (1 << category)) != 0
640 && (runp->avail & (1 << category)) == 0)
641 record_error (5, 0, _("\
642circular dependencies between locale definitions"));
643
644 if (copy_locale != NULL)
645 {
646 if (runp->categories[category].generic != NULL)
647 record_error (5, 0, _("\
648cannot add already read locale `%s' a second time"), name);
649 else
650 runp->categories[category].generic =
651 copy_locale->categories[category].generic;
652 }
653
654 runp->needed |= 1 << category;
655
656 return runp;
657}
658
659
660struct localedef_t *
661find_locale (int category, const char *name, const char *repertoire_name,
662 const struct charmap_t *charmap)
663{
664 struct localedef_t *result;
665
666 /* Find the locale, but do not generate it since this would be a bug. */
667 result = add_to_readlist (category, name, repertoire_name, 0, NULL);
668
669 assert (result != NULL);
670
671 if ((result->avail & (1 << category)) == 0
672 && locfile_read (result, charmap) != 0)
673 record_error (4, errno, _("\
674cannot open locale definition file `%s'"), result->name);
675
676 return result;
677}
678
679
680struct localedef_t *
681load_locale (int category, const char *name, const char *repertoire_name,
682 const struct charmap_t *charmap, struct localedef_t *copy_locale)
683{
684 struct localedef_t *result;
685
686 /* Generate the locale if it does not exist. */
687 result = add_to_readlist (category, name, repertoire_name, 1, copy_locale);
688
689 assert (result != NULL);
690
691 if ((result->avail & (1 << category)) == 0
692 && locfile_read (result, charmap) != 0)
693 record_error (4, errno, _("\
694cannot open locale definition file `%s'"), result->name);
695
696 return result;
697}
698