1/* Implementation of the locale program according to POSIX 9945-2.
2 Copyright (C) 1995-2021 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4 Contributed by Ulrich Drepper <drepper@cygnus.com>, 1995.
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published
8 by the Free Software Foundation; version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, see <https://www.gnu.org/licenses/>. */
18
19#ifdef HAVE_CONFIG_H
20# include <config.h>
21#endif
22
23#include <argp.h>
24#include <argz.h>
25#include <dirent.h>
26#include <errno.h>
27#include <error.h>
28#include <fcntl.h>
29#include <langinfo.h>
30#include <libintl.h>
31#include <limits.h>
32#include <locale.h>
33#include <search.h>
34#include <stdio.h>
35#include <stdio_ext.h>
36#include <stdlib.h>
37#include <string.h>
38#include <unistd.h>
39#include <stdint.h>
40#include <sys/mman.h>
41#include <sys/stat.h>
42
43#include "record-status.h"
44#include "localeinfo.h"
45#include "charmap-dir.h"
46#include "../locarchive.h"
47#include <programs/xmalloc.h>
48
49#define ARCHIVE_NAME COMPLOCALEDIR "/locale-archive"
50
51/* If set print the name of the category. */
52static int show_category_name;
53
54/* If set print the name of the item. */
55static int show_keyword_name;
56
57/* Print names of all available locales. */
58static int do_all;
59
60/* Print names of all available character maps. */
61static int do_charmaps = 0;
62
63/* Name and version of program. */
64static void print_version (FILE *stream, struct argp_state *state);
65void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version;
66
67/* Definitions of arguments for argp functions. */
68static const struct argp_option options[] =
69{
70 { NULL, 0, NULL, 0, N_("System information:") },
71 { "all-locales", 'a', NULL, OPTION_NO_USAGE,
72 N_("Write names of available locales") },
73 { "charmaps", 'm', NULL, OPTION_NO_USAGE,
74 N_("Write names of available charmaps") },
75 { NULL, 0, NULL, 0, N_("Modify output format:") },
76 { "category-name", 'c', NULL, 0, N_("Write names of selected categories") },
77 { "keyword-name", 'k', NULL, 0, N_("Write names of selected keywords") },
78 { "verbose", 'v', NULL, 0, N_("Print more information") },
79 { NULL, 0, NULL, 0, NULL }
80};
81
82/* Short description of program. */
83static const char doc[] = N_("Get locale-specific information.");
84
85/* Strings for arguments in help texts. */
86static const char args_doc[] = N_("NAME\n[-a|-m]");
87
88/* Prototype for option handler. */
89static error_t parse_opt (int key, char *arg, struct argp_state *state);
90
91/* Function to print some extra text in the help message. */
92static char *more_help (int key, const char *text, void *input);
93
94/* Data structure to communicate with argp functions. */
95static struct argp argp =
96{
97 options, parse_opt, args_doc, doc, NULL, more_help
98};
99
100
101/* We don't have these constants defined because we don't use them. Give
102 default values. */
103#define CTYPE_MB_CUR_MIN 0
104#define CTYPE_MB_CUR_MAX 0
105#define CTYPE_HASH_SIZE 0
106#define CTYPE_HASH_LAYERS 0
107#define CTYPE_CLASS 0
108#define CTYPE_TOUPPER_EB 0
109#define CTYPE_TOLOWER_EB 0
110#define CTYPE_TOUPPER_EL 0
111#define CTYPE_TOLOWER_EL 0
112
113/* Definition of the data structure which represents a category and its
114 items. */
115struct category
116{
117 int cat_id;
118 const char *name;
119 size_t number;
120 struct cat_item
121 {
122 int item_id;
123 const char *name;
124 enum { std, opt } status;
125 enum value_type value_type;
126 int min;
127 int max;
128 } *item_desc;
129};
130
131/* Simple helper macro. */
132#define NELEMS(arr) ((sizeof (arr)) / (sizeof (arr[0])))
133
134/* For some tricky stuff. */
135#define NO_PAREN(Item, More...) Item, ## More
136
137/* We have all categories defined in `categories.def'. Now construct
138 the description and data structure used for all categories. */
139#define DEFINE_ELEMENT(Item, More...) { Item, ## More },
140#define DEFINE_CATEGORY(category, name, items, postload) \
141 static struct cat_item category##_desc[] = \
142 { \
143 NO_PAREN items \
144 };
145
146#include "categories.def"
147#undef DEFINE_CATEGORY
148
149static struct category category[] =
150 {
151#define DEFINE_CATEGORY(category, name, items, postload) \
152 [category] = { _NL_NUM_##category, name, NELEMS (category##_desc), \
153 category##_desc },
154#include "categories.def"
155#undef DEFINE_CATEGORY
156 };
157#define NCATEGORIES NELEMS (category)
158
159
160/* Automatically set variable. */
161extern const char *__progname;
162
163/* helper function for extended name handling. */
164extern void locale_special (const char *name, int show_category_name,
165 int show_keyword_name);
166
167/* Prototypes for local functions. */
168static void print_LC_IDENTIFICATION (void *mapped, size_t size);
169static void print_LC_CTYPE (void *mapped, size_t size);
170static void write_locales (void);
171static int nameentcmp (const void *a, const void *b);
172static int write_archive_locales (void **all_datap, char *linebuf);
173static void write_charmaps (void);
174static void show_locale_vars (void);
175static void show_info (const char *name);
176static void try_setlocale (int category, const char *category_name);
177static char *quote_string (const char *input);
178static void setlocale_diagnostics (void);
179
180
181int
182main (int argc, char *argv[])
183{
184 int remaining;
185
186 /* Set initial values for global variables. */
187 show_category_name = 0;
188 show_keyword_name = 0;
189
190 /* Set locale. Do not set LC_ALL because the other categories must
191 not be affected (according to POSIX.2). */
192 try_setlocale (LC_CTYPE, "LC_CTYPE");
193 try_setlocale (LC_MESSAGES, "LC_MESSAGES");
194
195 /* Initialize the message catalog. */
196 textdomain (PACKAGE);
197
198 /* Parse and process arguments. */
199 argp_parse (&argp, argc, argv, 0, &remaining, NULL);
200
201 /* `-a' requests the names of all available locales. */
202 if (do_all != 0)
203 {
204 setlocale_diagnostics ();
205 try_setlocale (LC_COLLATE, "LC_COLLATE");
206 write_locales ();
207 exit (EXIT_SUCCESS);
208 }
209
210 /* `m' requests the names of all available charmaps. The names can be
211 used for the -f argument to localedef(1). */
212 if (do_charmaps != 0)
213 {
214 setlocale_diagnostics ();
215 write_charmaps ();
216 exit (EXIT_SUCCESS);
217 }
218
219 /* Specific information about the current locale are requested.
220 Change to this locale now. */
221 try_setlocale (LC_ALL, "LC_ALL");
222 setlocale_diagnostics ();
223
224 /* If no real argument is given we have to print the contents of the
225 current locale definition variables. These are LANG and the LC_*. */
226 if (remaining == argc && show_keyword_name == 0 && show_category_name == 0)
227 {
228 show_locale_vars ();
229 exit (EXIT_SUCCESS);
230 }
231
232 /* Process all given names. */
233 while (remaining < argc)
234 show_info (argv[remaining++]);
235
236 exit (EXIT_SUCCESS);
237}
238
239
240/* Handle program arguments. */
241static error_t
242parse_opt (int key, char *arg, struct argp_state *state)
243{
244 switch (key)
245 {
246 case 'a':
247 do_all = 1;
248 break;
249 case 'c':
250 show_category_name = 1;
251 break;
252 case 'm':
253 do_charmaps = 1;
254 break;
255 case 'k':
256 show_keyword_name = 1;
257 break;
258 case 'v':
259 verbose = 1;
260 break;
261 default:
262 return ARGP_ERR_UNKNOWN;
263 }
264 return 0;
265}
266
267
268static char *
269more_help (int key, const char *text, void *input)
270{
271 char *tp = NULL;
272 switch (key)
273 {
274 case ARGP_KEY_HELP_EXTRA:
275 /* We print some extra information. */
276 if (asprintf (&tp, gettext ("\
277For bug reporting instructions, please see:\n\
278%s.\n"), REPORT_BUGS_TO) < 0)
279 return NULL;
280 return tp;
281 default:
282 break;
283 }
284 return (char *) text;
285}
286
287
288/* Print the version information. */
289static void
290print_version (FILE *stream, struct argp_state *state)
291{
292 fprintf (stream, "locale %s%s\n", PKGVERSION, VERSION);
293 fprintf (stream, gettext ("\
294Copyright (C) %s Free Software Foundation, Inc.\n\
295This is free software; see the source for copying conditions. There is NO\n\
296warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
297"), "2021");
298 fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper");
299}
300
301
302/* Simple action function which prints arguments as strings. */
303static void
304print_names (const void *nodep, VISIT value, int level)
305{
306 if (value == postorder || value == leaf)
307 puts (*(char **) nodep);
308}
309
310
311static int
312select_dirs (const struct dirent *dirent)
313{
314 int result = 0;
315
316 if (strcmp (dirent->d_name, ".") != 0 && strcmp (dirent->d_name, "..") != 0)
317 {
318 mode_t mode = 0;
319
320 if (dirent->d_type != DT_UNKNOWN && dirent->d_type != DT_LNK)
321 mode = DTTOIF (dirent->d_type);
322 else
323 {
324 struct stat64 st;
325 char buf[sizeof (COMPLOCALEDIR)
326 + strlen (dirent->d_name) + 1];
327
328 stpcpy (stpcpy (stpcpy (buf, COMPLOCALEDIR), "/"),
329 dirent->d_name);
330
331 if (stat64 (buf, &st) == 0)
332 mode = st.st_mode;
333 }
334
335 result = S_ISDIR (mode);
336 }
337
338 return result;
339}
340
341
342static void
343print_LC_IDENTIFICATION (void *mapped, size_t size)
344{
345 /* Read the information from the file. */
346 struct
347 {
348 unsigned int magic;
349 unsigned int nstrings;
350 unsigned int strindex[0];
351 } *filedata = mapped;
352
353 if (filedata->magic == LIMAGIC (LC_IDENTIFICATION)
354 && (sizeof *filedata
355 + (filedata->nstrings
356 * sizeof (unsigned int))
357 <= size))
358 {
359 const char *str;
360
361#define HANDLE(idx, name) \
362 str = ((char *) mapped \
363 + filedata->strindex[_NL_ITEM_INDEX (_NL_IDENTIFICATION_##idx)]); \
364 if (*str != '\0') \
365 printf ("%9s | %s\n", name, str)
366 HANDLE (TITLE, "title");
367 HANDLE (SOURCE, "source");
368 HANDLE (ADDRESS, "address");
369 HANDLE (CONTACT, "contact");
370 HANDLE (EMAIL, "email");
371 HANDLE (TEL, "telephone");
372 HANDLE (FAX, "fax");
373 HANDLE (LANGUAGE, "language");
374 HANDLE (TERRITORY, "territory");
375 HANDLE (AUDIENCE, "audience");
376 HANDLE (APPLICATION, "application");
377 HANDLE (ABBREVIATION, "abbreviation");
378 HANDLE (REVISION, "revision");
379 HANDLE (DATE, "date");
380 }
381}
382
383
384static void
385print_LC_CTYPE (void *mapped, size_t size)
386{
387 struct
388 {
389 unsigned int magic;
390 unsigned int nstrings;
391 unsigned int strindex[0];
392 } *filedata = mapped;
393
394 if (filedata->magic == LIMAGIC (LC_CTYPE)
395 && (sizeof *filedata
396 + (filedata->nstrings
397 * sizeof (unsigned int))
398 <= size))
399 {
400 const char *str;
401
402 str = ((char *) mapped
403 + filedata->strindex[_NL_ITEM_INDEX (_NL_CTYPE_CODESET_NAME)]);
404 if (*str != '\0')
405 printf (" codeset | %s\n", str);
406 }
407}
408
409
410/* Write the names of all available locales to stdout. We have some
411 sources of the information: the contents of the locale directory
412 and the locale.alias file. To avoid duplicates and print the
413 result is a reasonable order we put all entries is a search tree
414 and print them afterwards. */
415static void
416write_locales (void)
417{
418 char linebuf[80];
419 void *all_data = NULL;
420 struct dirent **dirents;
421 int ndirents;
422 int cnt;
423 char *alias_path;
424 size_t alias_path_len;
425 char *entry;
426 int first_locale = 1;
427
428#define PUT(name) tsearch (name, &all_data, \
429 (int (*) (const void *, const void *)) strcoll)
430#define GET(name) tfind (name, &all_data, \
431 (int (*) (const void *, const void *)) strcoll)
432
433 /* `POSIX' locale is always available (POSIX.2 4.34.3). */
434 PUT ("POSIX");
435 /* And so is the "C" locale. */
436 PUT ("C");
437
438 memset (linebuf, '-', sizeof (linebuf) - 1);
439 linebuf[sizeof (linebuf) - 1] = '\0';
440
441 /* First scan the locale archive. */
442 if (write_archive_locales (&all_data, linebuf))
443 first_locale = 0;
444
445 /* Now we can look for all files in the directory. */
446 ndirents = scandir (COMPLOCALEDIR, &dirents, select_dirs,
447 alphasort);
448 for (cnt = 0; cnt < ndirents; ++cnt)
449 {
450 /* Test whether at least the LC_CTYPE data is there. Some
451 directories only contain translations. */
452 char buf[sizeof (COMPLOCALEDIR)
453 + strlen (dirents[cnt]->d_name)
454 + sizeof "/LC_IDENTIFICATION"];
455 char *enddir;
456 struct stat64 st;
457
458 stpcpy (enddir = stpcpy (stpcpy (stpcpy (buf,
459 COMPLOCALEDIR),
460 "/"),
461 dirents[cnt]->d_name),
462 "/LC_IDENTIFICATION");
463
464 if (stat64 (buf, &st) == 0 && S_ISREG (st.st_mode))
465 {
466 if (verbose && GET (dirents[cnt]->d_name) == NULL)
467 {
468 /* Provide some nice output of all kinds of
469 information. */
470 int fd;
471
472 if (! first_locale)
473 putchar_unlocked ('\n');
474 first_locale = 0;
475
476 printf ("locale: %-15.15s directory: %.*s\n%s\n",
477 dirents[cnt]->d_name, (int) (enddir - buf), buf,
478 linebuf);
479
480 fd = open64 (buf, O_RDONLY);
481 if (fd != -1)
482 {
483 void *mapped = mmap64 (NULL, st.st_size, PROT_READ,
484 MAP_SHARED, fd, 0);
485 if (mapped != MAP_FAILED)
486 {
487 print_LC_IDENTIFICATION (mapped, st.st_size);
488
489 munmap (mapped, st.st_size);
490 }
491
492 close (fd);
493
494 /* Now try to get the charset information. */
495 strcpy (enddir, "/LC_CTYPE");
496 fd = open64 (buf, O_RDONLY);
497 if (fd != -1 && fstat64 (fd, &st) >= 0
498 && ((mapped = mmap64 (NULL, st.st_size, PROT_READ,
499 MAP_SHARED, fd, 0))
500 != MAP_FAILED))
501 {
502 print_LC_CTYPE (mapped, st.st_size);
503
504 munmap (mapped, st.st_size);
505 }
506
507 if (fd != -1)
508 close (fd);
509 }
510 }
511
512 /* If the verbose format is not selected we simply
513 collect the names. */
514 PUT (xstrdup (dirents[cnt]->d_name));
515 }
516 }
517 if (ndirents > 0)
518 free (dirents);
519
520 /* Now read the locale.alias files. */
521 if (argz_create_sep (LOCALE_ALIAS_PATH, ':', &alias_path, &alias_path_len))
522 error (1, errno, gettext ("while preparing output"));
523
524 entry = NULL;
525 while ((entry = argz_next (alias_path, alias_path_len, entry)))
526 {
527 static const char aliasfile[] = "/locale.alias";
528 FILE *fp;
529 char full_name[strlen (entry) + sizeof aliasfile];
530
531 stpcpy (stpcpy (full_name, entry), aliasfile);
532 fp = fopen (full_name, "rm");
533 if (fp == NULL)
534 /* Ignore non-existing files. */
535 continue;
536
537 /* No threads present. */
538 __fsetlocking (fp, FSETLOCKING_BYCALLER);
539
540 while (! feof_unlocked (fp))
541 {
542 /* It is a reasonable approach to use a fix buffer here
543 because
544 a) we are only interested in the first two fields
545 b) these fields must be usable as file names and so must
546 not be that long */
547 char buf[BUFSIZ];
548 char *alias;
549 char *value;
550 char *cp;
551
552 if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
553 /* EOF reached. */
554 break;
555
556 cp = buf;
557 /* Ignore leading white space. */
558 while (isspace (cp[0]) && cp[0] != '\n')
559 ++cp;
560
561 /* A leading '#' signals a comment line. */
562 if (cp[0] != '\0' && cp[0] != '#' && cp[0] != '\n')
563 {
564 alias = cp++;
565 while (cp[0] != '\0' && !isspace (cp[0]))
566 ++cp;
567 /* Terminate alias name. */
568 if (cp[0] != '\0')
569 *cp++ = '\0';
570
571 /* Now look for the beginning of the value. */
572 while (isspace (cp[0]))
573 ++cp;
574
575 if (cp[0] != '\0')
576 {
577 value = cp++;
578 while (cp[0] != '\0' && !isspace (cp[0]))
579 ++cp;
580 /* Terminate value. */
581 if (cp[0] == '\n')
582 {
583 /* This has to be done to make the following
584 test for the end of line possible. We are
585 looking for the terminating '\n' which do not
586 overwrite here. */
587 *cp++ = '\0';
588 *cp = '\n';
589 }
590 else if (cp[0] != '\0')
591 *cp++ = '\0';
592
593 /* Add the alias. */
594 if (! verbose && GET (value) != NULL)
595 PUT (xstrdup (alias));
596 }
597 }
598
599 /* Possibly not the whole line fits into the buffer.
600 Ignore the rest of the line. */
601 while (strchr (cp, '\n') == NULL)
602 {
603 cp = buf;
604 if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
605 /* Make sure the inner loop will be left. The outer
606 loop will exit at the `feof' test. */
607 *cp = '\n';
608 }
609 }
610
611 fclose (fp);
612 }
613
614 if (! verbose)
615 {
616 twalk (all_data, print_names);
617 }
618}
619
620
621struct nameent
622{
623 char *name;
624 uint32_t locrec_offset;
625};
626
627
628static int
629nameentcmp (const void *a, const void *b)
630{
631 return strcoll (((const struct nameent *) a)->name,
632 ((const struct nameent *) b)->name);
633}
634
635
636static int
637write_archive_locales (void **all_datap, char *linebuf)
638{
639 struct stat64 st;
640 void *all_data = *all_datap;
641 size_t len = 0;
642 struct locarhead *head;
643 struct namehashent *namehashtab;
644 char *addr = MAP_FAILED;
645 int fd, ret = 0;
646 uint32_t cnt;
647
648 fd = open64 (ARCHIVE_NAME, O_RDONLY);
649 if (fd < 0)
650 return 0;
651
652 if (fstat64 (fd, &st) < 0 || st.st_size < sizeof (*head))
653 goto error_out;
654
655 len = st.st_size;
656 addr = mmap64 (NULL, len, PROT_READ, MAP_SHARED, fd, 0);
657 if (addr == MAP_FAILED)
658 goto error_out;
659
660 head = (struct locarhead *) addr;
661 if (head->namehash_offset + head->namehash_size > len
662 || head->string_offset + head->string_size > len
663 || head->locrectab_offset + head->locrectab_size > len
664 || head->sumhash_offset + head->sumhash_size > len)
665 goto error_out;
666
667 namehashtab = (struct namehashent *) (addr + head->namehash_offset);
668 if (! verbose)
669 {
670 for (cnt = 0; cnt < head->namehash_size; ++cnt)
671 if (namehashtab[cnt].locrec_offset != 0)
672 {
673 PUT (xstrdup (addr + namehashtab[cnt].name_offset));
674 ++ret;
675 }
676 }
677 else
678 {
679 struct nameent *names;
680 uint32_t used;
681
682 names = (struct nameent *) xmalloc (head->namehash_used
683 * sizeof (struct nameent));
684 for (cnt = used = 0; cnt < head->namehash_size; ++cnt)
685 if (namehashtab[cnt].locrec_offset != 0)
686 {
687 names[used].name = addr + namehashtab[cnt].name_offset;
688 names[used++].locrec_offset = namehashtab[cnt].locrec_offset;
689 }
690
691 /* Sort the names. */
692 qsort (names, used, sizeof (struct nameent), nameentcmp);
693
694 for (cnt = 0; cnt < used; ++cnt)
695 {
696 struct locrecent *locrec;
697
698 PUT (xstrdup (names[cnt].name));
699
700 if (cnt)
701 putchar_unlocked ('\n');
702
703 printf ("locale: %-15.15s archive: " ARCHIVE_NAME "\n%s\n",
704 names[cnt].name, linebuf);
705
706 locrec = (struct locrecent *) (addr + names[cnt].locrec_offset);
707
708 print_LC_IDENTIFICATION (addr
709 + locrec->record[LC_IDENTIFICATION].offset,
710 locrec->record[LC_IDENTIFICATION].len);
711
712 print_LC_CTYPE (addr + locrec->record[LC_CTYPE].offset,
713 locrec->record[LC_CTYPE].len);
714 }
715
716 free (names);
717
718 ret = used;
719 }
720
721error_out:
722 if (addr != MAP_FAILED)
723 munmap (addr, len);
724 close (fd);
725 *all_datap = all_data;
726 return ret;
727}
728
729
730/* Write the names of all available character maps to stdout. */
731static void
732write_charmaps (void)
733{
734 void *all_data = NULL;
735 CHARMAP_DIR *dir;
736 const char *dirent;
737
738 /* Look for all files in the charmap directory. */
739 dir = charmap_opendir (CHARMAP_PATH);
740 if (dir == NULL)
741 return;
742
743 while ((dirent = charmap_readdir (dir)) != NULL)
744 {
745 char **aliases;
746 char **p;
747
748 PUT (xstrdup (dirent));
749
750 aliases = charmap_aliases (CHARMAP_PATH, dirent);
751
752#if 0
753 /* Add the code_set_name and the aliases. */
754 for (p = aliases; *p; p++)
755 PUT (xstrdup (*p));
756#else
757 /* Add the code_set_name only. Most aliases are obsolete. */
758 p = aliases;
759 if (*p)
760 PUT (xstrdup (*p));
761#endif
762
763 charmap_free_aliases (aliases);
764 }
765
766 charmap_closedir (dir);
767
768 twalk (all_data, print_names);
769}
770
771/* Print a properly quoted assignment of NAME with VAL, using double
772 quotes iff DQUOTE is true. */
773static void
774print_assignment (const char *name, const char *val, bool dquote)
775{
776 printf ("%s=", name);
777 if (dquote)
778 putchar ('"');
779 while (*val != '\0')
780 {
781 size_t segment
782 = strcspn (val, dquote ? "$`\"\\" : "~|&;<>()$`\\\"' \t\n");
783 printf ("%.*s", (int) segment, val);
784 val += segment;
785 if (*val == '\0')
786 break;
787 putchar ('\\');
788 putchar (*val++);
789 }
790 if (dquote)
791 putchar ('"');
792 putchar ('\n');
793}
794
795/* We have to show the contents of the environments determining the
796 locale. */
797static void
798show_locale_vars (void)
799{
800 const char *lcall = getenv ("LC_ALL") ?: "";
801 const char *lang = getenv ("LANG") ?: "";
802
803 /* LANG has to be the first value. */
804 print_assignment ("LANG", lang, false);
805
806 /* Now all categories in an unspecified order. */
807 for (size_t cat_no = 0; cat_no < NCATEGORIES; ++cat_no)
808 if (cat_no != LC_ALL)
809 {
810 const char *name = category[cat_no].name;
811 const char *val = getenv (name);
812
813 if (lcall[0] != '\0' || val == NULL)
814 print_assignment (name,
815 lcall[0] != '\0' ? lcall
816 : lang[0] != '\0' ? lang
817 : "POSIX",
818 true);
819 else
820 print_assignment (name, val, false);
821 }
822
823 /* The last is the LC_ALL value. */
824 print_assignment ("LC_ALL", lcall, false);
825}
826
827
828/* Subroutine of show_info, below. */
829static void
830print_item (struct cat_item *item)
831{
832 switch (item->value_type)
833 {
834 case string:
835 if (show_keyword_name)
836 printf ("%s=\"", item->name);
837 fputs (nl_langinfo (item->item_id) ? : "", stdout);
838 if (show_keyword_name)
839 putchar ('"');
840 putchar ('\n');
841 break;
842 case stringarray:
843 {
844 const char *val;
845 int cnt;
846
847 if (show_keyword_name)
848 printf ("%s=\"", item->name);
849
850 for (cnt = 0; cnt < item->max - 1; ++cnt)
851 {
852 val = nl_langinfo (item->item_id + cnt);
853 if (val != NULL)
854 fputs (val, stdout);
855 putchar (';');
856 }
857
858 val = nl_langinfo (item->item_id + cnt);
859 if (val != NULL)
860 fputs (val, stdout);
861
862 if (show_keyword_name)
863 putchar ('"');
864 putchar ('\n');
865 }
866 break;
867 case stringlist:
868 {
869 int first = 1;
870 const char *val = nl_langinfo (item->item_id) ? : "";
871
872 if (show_keyword_name)
873 printf ("%s=", item->name);
874
875 for (int cnt = 0; cnt < item->max && *val != '\0'; ++cnt)
876 {
877 printf ("%s%s%s%s", first ? "" : ";",
878 show_keyword_name ? "\"" : "", val,
879 show_keyword_name ? "\"" : "");
880 val = strchr (val, '\0') + 1;
881 first = 0;
882 }
883 putchar ('\n');
884 }
885 break;
886 case byte:
887 {
888 const char *val = nl_langinfo (item->item_id);
889
890 if (show_keyword_name)
891 printf ("%s=", item->name);
892
893 if (val != NULL)
894 printf ("%d", *val == '\377' ? -1 : *val);
895 putchar ('\n');
896 }
897 break;
898 case bytearray:
899 {
900 const char *val = nl_langinfo (item->item_id);
901 int cnt = val ? strlen (val) : 0;
902
903 if (show_keyword_name)
904 printf ("%s=", item->name);
905
906 while (cnt > 1)
907 {
908 printf ("%d;", *val == '\177' ? -1 : *val);
909 --cnt;
910 ++val;
911 }
912
913 printf ("%d\n", cnt == 0 || *val == '\177' ? -1 : *val);
914 }
915 break;
916 case word:
917 {
918 union { unsigned int word; char *string; } val;
919 val.string = nl_langinfo (item->item_id);
920 if (show_keyword_name)
921 printf ("%s=", item->name);
922
923 printf ("%d\n", val.word);
924 }
925 break;
926 case wordarray:
927 {
928 int first = 1;
929 union { unsigned int *wordarray; char *string; } val;
930
931 val.string = nl_langinfo (item->item_id);
932 if (show_keyword_name)
933 printf ("%s=", item->name);
934
935 for (int cnt = 0; cnt < item->max; ++cnt)
936 {
937 printf ("%s%d", first ? "" : ";", val.wordarray[cnt]);
938 first = 0;
939 }
940 putchar ('\n');
941 }
942 break;
943 case wstring:
944 case wstringarray:
945 case wstringlist:
946 /* We don't print wide character information since the same
947 information is available in a multibyte string. */
948 default:
949 break;
950 }
951}
952
953/* Show the information request for NAME. */
954static void
955show_info (const char *name)
956{
957 for (size_t cat_no = 0; cat_no < NCATEGORIES; ++cat_no)
958 if (cat_no != LC_ALL)
959 {
960 if (strcmp (name, category[cat_no].name) == 0)
961 /* Print the whole category. */
962 {
963 if (show_category_name != 0)
964 puts (category[cat_no].name);
965
966 for (size_t item_no = 0;
967 item_no < category[cat_no].number;
968 ++item_no)
969 print_item (&category[cat_no].item_desc[item_no]);
970
971 return;
972 }
973
974 for (size_t item_no = 0; item_no < category[cat_no].number; ++item_no)
975 if (strcmp (name, category[cat_no].item_desc[item_no].name) == 0)
976 {
977 if (show_category_name != 0)
978 puts (category[cat_no].name);
979
980 print_item (&category[cat_no].item_desc[item_no]);
981 return;
982 }
983 }
984
985 /* The name is not a standard one.
986 For testing and perhaps advanced use allow some more symbols. */
987 locale_special (name, show_category_name, show_keyword_name);
988}
989
990/* Set to true by try_setlocale if setlocale fails. Used by
991 setlocale_diagnostics. */
992static bool setlocale_failed;
993
994/* Call setlocale, with non-fatal error reporting. */
995static void
996try_setlocale (int category, const char *category_name)
997{
998 if (setlocale (category, "") == NULL)
999 {
1000 error (0, errno, gettext ("Cannot set %s to default locale"),
1001 category_name);
1002 setlocale_failed = true;
1003 }
1004}
1005
1006/* Return a quoted version of the passed string, or NULL on error. */
1007static char *
1008quote_string (const char *input)
1009{
1010 char *buffer;
1011 size_t length;
1012 FILE *stream = open_memstream (&buffer, &length);
1013 if (stream == NULL)
1014 return NULL;
1015
1016 while (true)
1017 {
1018 unsigned char ch = *input++;
1019 if (ch == '\0')
1020 break;
1021
1022 /* Use C backslash escapes for those control characters for
1023 which they are defined. */
1024 switch (ch)
1025 {
1026 case '\a':
1027 putc_unlocked ('\\', stream);
1028 putc_unlocked ('a', stream);
1029 break;
1030 case '\b':
1031 putc_unlocked ('\\', stream);
1032 putc_unlocked ('b', stream);
1033 break;
1034 case '\f':
1035 putc_unlocked ('\\', stream);
1036 putc_unlocked ('f', stream);
1037 break;
1038 case '\n':
1039 putc_unlocked ('\\', stream);
1040 putc_unlocked ('n', stream);
1041 break;
1042 case '\r':
1043 putc_unlocked ('\\', stream);
1044 putc_unlocked ('r', stream);
1045 break;
1046 case '\t':
1047 putc_unlocked ('\\', stream);
1048 putc_unlocked ('t', stream);
1049 break;
1050 case '\v':
1051 putc_unlocked ('\\', stream);
1052 putc_unlocked ('v', stream);
1053 break;
1054 case '\\':
1055 case '\'':
1056 case '\"':
1057 putc_unlocked ('\\', stream);
1058 putc_unlocked (ch, stream);
1059 break;
1060 default:
1061 if (ch < ' ' || ch > '~')
1062 /* Use octal sequences because they are fixed width,
1063 unlike hexadecimal sequences. */
1064 fprintf (stream, "\\%03o", ch);
1065 else
1066 putc_unlocked (ch, stream);
1067 }
1068 }
1069
1070 if (ferror (stream))
1071 {
1072 fclose (stream);
1073 free (buffer);
1074 return NULL;
1075 }
1076 if (fclose (stream) != 0)
1077 {
1078 free (buffer);
1079 return NULL;
1080 }
1081
1082 return buffer;
1083}
1084
1085/* Print additional information if there was a setlocale error (during
1086 try_setlocale). */
1087static void
1088setlocale_diagnostics (void)
1089{
1090 if (setlocale_failed)
1091 {
1092 const char *locpath = getenv ("LOCPATH");
1093 if (locpath != NULL)
1094 {
1095 char *quoted = quote_string (locpath);
1096 if (quoted != NULL)
1097 fprintf (stderr,
1098 gettext ("\
1099warning: The LOCPATH variable is set to \"%s\"\n"),
1100 quoted);
1101 else
1102 fputs ("warning: The LOCPATH variable is set\n", stderr);
1103 free (quoted);
1104 }
1105 }
1106}
1107